cli-test-framework 0.3.5__tar.gz → 0.3.7__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.3.5/src/cli_test_framework.egg-info → cli_test_framework-0.3.7}/PKG-INFO +45 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/README.md +44 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/docs/user_manual.md +23 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/setup.py +1 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/__init__.py +1 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/commands/compare.py +7 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/factory.py +1 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/h5_comparator.py +98 -42
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7/src/cli_test_framework.egg-info}/PKG-INFO +45 -1
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/SOURCES.txt +1 -0
- cli_test_framework-0.3.7/tests/test_filter_demo.py +103 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/CHANGELOG.md +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/MANIFEST.in +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/pyproject.toml +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/setup.cfg +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/cli.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/commands/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/assertions.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/base_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/parallel_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/process_worker.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/setup.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/test_case.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/result.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/json_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/parallel_json_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/yaml_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/utils/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/utils/path_resolver.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/utils/report_generator.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/requires.txt +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/top_level.txt +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/__init__.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/fixtures/test_cases.json +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/fixtures/test_cases.yaml +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/fixtures/test_cases1.json +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/fixtures/test_with_setup.json +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/fixtures/test_with_setup.yaml +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/performance_test.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test1.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_comprehensive_space.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_parallel_runner.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_parallel_space.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_report.txt +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_runners.py +0 -0
- {cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/test_setup_module.py +0 -0
{cli_test_framework-0.3.5/src/cli_test_framework.egg-info → cli_test_framework-0.3.7}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-test-framework
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
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/ozil111/cli-test-framework
|
|
6
6
|
Author: Xiaotong Wang
|
|
@@ -233,6 +233,11 @@ compare-files data1.h5 data2.h5 \
|
|
|
233
233
|
compare-files data1.h5 data2.h5 \
|
|
234
234
|
--h5-table-regex "group1/.*" \
|
|
235
235
|
--h5-structure-only
|
|
236
|
+
|
|
237
|
+
# Use comma-separated table names with regex (New in 0.3.7)
|
|
238
|
+
compare-files data1.h5 data2.h5 \
|
|
239
|
+
--h5-table-regex "table1,table2,table3" \
|
|
240
|
+
--h5-rtol 1e-6
|
|
236
241
|
```
|
|
237
242
|
|
|
238
243
|
#### Binary Comparison
|
|
@@ -519,3 +524,42 @@ The user manual includes:
|
|
|
519
524
|
**🚀 Ready to supercharge your testing workflow with setup modules, parallel execution and advanced file comparison!**
|
|
520
525
|
|
|
521
526
|
For detailed parallel testing guide, see: [PARALLEL_TESTING_GUIDE.md](https://github.com/ozil111/cli-test-framework/blob/main/PARALLEL_TESTING_GUIDE.md)
|
|
527
|
+
|
|
528
|
+
# 支持数据过滤(New in 0.3.6)
|
|
529
|
+
|
|
530
|
+
你可以通过 `--h5-data-filter` 选项只比较满足特定条件的数据。例如:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# 只比较大于 1e-6 的数据
|
|
534
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '>1e-6'
|
|
535
|
+
|
|
536
|
+
# 只比较绝对值大于 1e-6 的数据
|
|
537
|
+
compare-files data1.h5 data2.h5 --h5-data-filter 'abs>1e-6'
|
|
538
|
+
|
|
539
|
+
# 只比较小于等于 0.01 的数据
|
|
540
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
支持的表达式包括:`>`, `>=`, `<`, `<=`, `==`,以及 `abs` 前缀(绝对值过滤)。
|
|
544
|
+
|
|
545
|
+
# 版本更新日志
|
|
546
|
+
|
|
547
|
+
## 0.3.7 (Latest)
|
|
548
|
+
|
|
549
|
+
### 🐛 Bug Fixes
|
|
550
|
+
- **Fixed H5 table regex matching**: `--h5-table-regex=table1,table2` now correctly matches both `table1` and `table2` instead of treating the entire string as a single regex pattern
|
|
551
|
+
- **Enhanced regex pattern support**: Multiple comma-separated table names are now supported in `--h5-table-regex` parameter
|
|
552
|
+
|
|
553
|
+
### ✨ New Features
|
|
554
|
+
- **Improved HDF5 comparison**: Better handling of multiple table selection with regex patterns
|
|
555
|
+
- **Enhanced debug output**: More detailed logging for HDF5 table matching process
|
|
556
|
+
|
|
557
|
+
### 🔧 Improvements
|
|
558
|
+
- **Backward compatibility**: All existing functionality remains unchanged
|
|
559
|
+
- **Better error handling**: More informative error messages for regex pattern parsing
|
|
560
|
+
|
|
561
|
+
## 0.3.6
|
|
562
|
+
|
|
563
|
+
### ✨ New Features
|
|
564
|
+
- **Data filtering for HDF5 files**: Added `--h5-data-filter` option to compare only data meeting specific criteria
|
|
565
|
+
- **Enhanced HDF5 comparison**: Support for absolute value filtering and various comparison operators
|
|
@@ -198,6 +198,11 @@ compare-files data1.h5 data2.h5 \
|
|
|
198
198
|
compare-files data1.h5 data2.h5 \
|
|
199
199
|
--h5-table-regex "group1/.*" \
|
|
200
200
|
--h5-structure-only
|
|
201
|
+
|
|
202
|
+
# Use comma-separated table names with regex (New in 0.3.7)
|
|
203
|
+
compare-files data1.h5 data2.h5 \
|
|
204
|
+
--h5-table-regex "table1,table2,table3" \
|
|
205
|
+
--h5-rtol 1e-6
|
|
201
206
|
```
|
|
202
207
|
|
|
203
208
|
#### Binary Comparison
|
|
@@ -484,3 +489,42 @@ The user manual includes:
|
|
|
484
489
|
**🚀 Ready to supercharge your testing workflow with setup modules, parallel execution and advanced file comparison!**
|
|
485
490
|
|
|
486
491
|
For detailed parallel testing guide, see: [PARALLEL_TESTING_GUIDE.md](https://github.com/ozil111/cli-test-framework/blob/main/PARALLEL_TESTING_GUIDE.md)
|
|
492
|
+
|
|
493
|
+
# 支持数据过滤(New in 0.3.6)
|
|
494
|
+
|
|
495
|
+
你可以通过 `--h5-data-filter` 选项只比较满足特定条件的数据。例如:
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# 只比较大于 1e-6 的数据
|
|
499
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '>1e-6'
|
|
500
|
+
|
|
501
|
+
# 只比较绝对值大于 1e-6 的数据
|
|
502
|
+
compare-files data1.h5 data2.h5 --h5-data-filter 'abs>1e-6'
|
|
503
|
+
|
|
504
|
+
# 只比较小于等于 0.01 的数据
|
|
505
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
支持的表达式包括:`>`, `>=`, `<`, `<=`, `==`,以及 `abs` 前缀(绝对值过滤)。
|
|
509
|
+
|
|
510
|
+
# 版本更新日志
|
|
511
|
+
|
|
512
|
+
## 0.3.7 (Latest)
|
|
513
|
+
|
|
514
|
+
### 🐛 Bug Fixes
|
|
515
|
+
- **Fixed H5 table regex matching**: `--h5-table-regex=table1,table2` now correctly matches both `table1` and `table2` instead of treating the entire string as a single regex pattern
|
|
516
|
+
- **Enhanced regex pattern support**: Multiple comma-separated table names are now supported in `--h5-table-regex` parameter
|
|
517
|
+
|
|
518
|
+
### ✨ New Features
|
|
519
|
+
- **Improved HDF5 comparison**: Better handling of multiple table selection with regex patterns
|
|
520
|
+
- **Enhanced debug output**: More detailed logging for HDF5 table matching process
|
|
521
|
+
|
|
522
|
+
### 🔧 Improvements
|
|
523
|
+
- **Backward compatibility**: All existing functionality remains unchanged
|
|
524
|
+
- **Better error handling**: More informative error messages for regex pattern parsing
|
|
525
|
+
|
|
526
|
+
## 0.3.6
|
|
527
|
+
|
|
528
|
+
### ✨ New Features
|
|
529
|
+
- **Data filtering for HDF5 files**: Added `--h5-data-filter` option to compare only data meeting specific criteria
|
|
530
|
+
- **Enhanced HDF5 comparison**: Support for absolute value filtering and various comparison operators
|
|
@@ -319,8 +319,31 @@ compare-files data1.h5 data2.h5 --h5-table table1,table2
|
|
|
319
319
|
|
|
320
320
|
# Compare with numerical tolerance
|
|
321
321
|
compare-files data1.h5 data2.h5 --h5-rtol 1e-5 --h5-atol 1e-8
|
|
322
|
+
|
|
323
|
+
# Use regex patterns for table selection
|
|
324
|
+
compare-files data1.h5 data2.h5 --h5-table-regex "table.*"
|
|
325
|
+
|
|
326
|
+
# Use comma-separated table names with regex (New in 0.3.7)
|
|
327
|
+
compare-files data1.h5 data2.h5 --h5-table-regex "table1,table2,table3"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Data Filtering (New in 0.3.7)
|
|
331
|
+
|
|
332
|
+
You can use the `--h5-data-filter` option to only compare data that meets a certain condition. For example:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Only compare values greater than 1e-6
|
|
336
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '>1e-6'
|
|
337
|
+
|
|
338
|
+
# Only compare absolute values greater than 1e-6
|
|
339
|
+
compare-files data1.h5 data2.h5 --h5-data-filter 'abs>1e-6'
|
|
340
|
+
|
|
341
|
+
# Only compare values less than or equal to 0.01
|
|
342
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
|
|
322
343
|
```
|
|
323
344
|
|
|
345
|
+
Supported filter expressions: `>`, `>=`, `<`, `<=`, `==`, and `abs` prefix for absolute value filtering.
|
|
346
|
+
|
|
324
347
|
### Binary File Comparison
|
|
325
348
|
```bash
|
|
326
349
|
# Compare with similarity check
|
|
@@ -8,7 +8,7 @@ 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.3.
|
|
11
|
+
version="0.3.7",
|
|
12
12
|
author="Xiaotong Wang",
|
|
13
13
|
author_email="xiaotongwang98@gmail.com",
|
|
14
14
|
description="A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.",
|
|
@@ -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.3.
|
|
8
|
+
__version__ = "0.3.7"
|
|
9
9
|
__author__ = "Xiaotong Wang"
|
|
10
10
|
__email__ = "xiaotongwang98@gmail.com"
|
|
11
11
|
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/commands/compare.py
RENAMED
|
@@ -58,7 +58,7 @@ def parse_arguments():
|
|
|
58
58
|
# H5 comparison options
|
|
59
59
|
h5_group = parser.add_argument_group('HDF5 comparison options')
|
|
60
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="
|
|
61
|
+
h5_group.add_argument("--h5-table-regex", help="Comma-separated list of regular expression patterns to match table names in HDF5 files. Each pattern is matched independently.")
|
|
62
62
|
h5_group.add_argument("--h5-structure-only", action="store_true",
|
|
63
63
|
help="Only compare HDF5 file structure without comparing content")
|
|
64
64
|
h5_group.add_argument("--h5-show-content-diff", action="store_true",
|
|
@@ -67,6 +67,10 @@ def parse_arguments():
|
|
|
67
67
|
help="Relative tolerance for numerical comparison in HDF5 files")
|
|
68
68
|
h5_group.add_argument("--h5-atol", type=float, default=1e-8,
|
|
69
69
|
help="Absolute tolerance for numerical comparison in HDF5 files")
|
|
70
|
+
h5_group.add_argument("--h5-data-filter", type=str,
|
|
71
|
+
help="Data filter to apply before comparison. "
|
|
72
|
+
"Example: '>1e-6', '<=0.01', 'abs>1e-9'. "
|
|
73
|
+
"Filters out data that does not meet the criteria from BOTH files before comparison.")
|
|
70
74
|
h5_group.add_argument("--h5-no-expand-path", dest="h5_expand_path", action="store_false",
|
|
71
75
|
help="Do not expand HDF5 group paths to compare all sub-items.")
|
|
72
76
|
|
|
@@ -150,6 +154,8 @@ def main():
|
|
|
150
154
|
comparator_kwargs["show_content_diff"] = args.h5_show_content_diff
|
|
151
155
|
comparator_kwargs["rtol"] = args.h5_rtol
|
|
152
156
|
comparator_kwargs["atol"] = args.h5_atol
|
|
157
|
+
if args.h5_data_filter:
|
|
158
|
+
comparator_kwargs["data_filter"] = args.h5_data_filter
|
|
153
159
|
comparator_kwargs["expand_path"] = args.h5_expand_path
|
|
154
160
|
|
|
155
161
|
if file_type == "binary":
|
|
@@ -65,7 +65,7 @@ class ComparatorFactory:
|
|
|
65
65
|
if file_type.lower() == 'h5':
|
|
66
66
|
# H5 comparator accepts specific parameters
|
|
67
67
|
h5_kwargs = {k: v for k, v in kwargs.items()
|
|
68
|
-
if k in ['tables', 'table_regex', 'encoding', 'chunk_size', 'verbose', 'structure_only', 'show_content_diff', 'debug', 'rtol', 'atol', 'expand_path']}
|
|
68
|
+
if k in ['tables', 'table_regex', 'encoding', 'chunk_size', 'verbose', 'structure_only', 'show_content_diff', 'debug', 'rtol', 'atol', 'expand_path', 'data_filter']}
|
|
69
69
|
return comparator_class(**h5_kwargs)
|
|
70
70
|
elif file_type.lower() == 'binary':
|
|
71
71
|
# Binary comparator accepts all parameters, including num_threads
|
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
7
|
class H5Comparator(BaseComparator):
|
|
8
|
-
def __init__(self, tables=None, table_regex=None, structure_only=False, show_content_diff=False, debug=False, rtol=1e-5, atol=1e-8, expand_path=True, **kwargs):
|
|
8
|
+
def __init__(self, tables=None, table_regex=None, structure_only=False, show_content_diff=False, debug=False, rtol=1e-5, atol=1e-8, expand_path=True, data_filter=None, **kwargs):
|
|
9
9
|
"""
|
|
10
10
|
Initialize H5 comparator
|
|
11
11
|
:param tables: List of table names to compare. If None, compare all tables
|
|
@@ -16,6 +16,7 @@ class H5Comparator(BaseComparator):
|
|
|
16
16
|
:param rtol: Relative tolerance for numerical comparison
|
|
17
17
|
:param atol: Absolute tolerance for numerical comparison
|
|
18
18
|
:param expand_path: If True, expand group paths to compare all sub-items. Defaults to True.
|
|
19
|
+
:param data_filter: String filter expression for data comparison (e.g., '>1e-6', 'abs>1e-9')
|
|
19
20
|
"""
|
|
20
21
|
super().__init__(**kwargs)
|
|
21
22
|
self.tables = tables
|
|
@@ -25,12 +26,14 @@ class H5Comparator(BaseComparator):
|
|
|
25
26
|
self.rtol = rtol
|
|
26
27
|
self.atol = atol
|
|
27
28
|
self.expand_path = expand_path
|
|
29
|
+
self.data_filter = data_filter
|
|
30
|
+
self.filter_func = self._parse_filter()
|
|
28
31
|
|
|
29
32
|
# Set debug level if verbose is enabled
|
|
30
33
|
if kwargs.get('verbose', False) or debug:
|
|
31
34
|
self.logger.setLevel(logging.DEBUG)
|
|
32
35
|
|
|
33
|
-
self.logger.debug(f"Initialized H5Comparator with structure_only={structure_only}, show_content_diff={show_content_diff}, rtol={rtol}, atol={atol}, expand_path={expand_path}")
|
|
36
|
+
self.logger.debug(f"Initialized H5Comparator with structure_only={structure_only}, show_content_diff={show_content_diff}, rtol={rtol}, atol={atol}, expand_path={expand_path}, data_filter={data_filter}")
|
|
34
37
|
if table_regex:
|
|
35
38
|
self.logger.debug(f"Using table regex pattern: {table_regex}")
|
|
36
39
|
|
|
@@ -120,31 +123,36 @@ class H5Comparator(BaseComparator):
|
|
|
120
123
|
|
|
121
124
|
if self.tables or self.table_regex:
|
|
122
125
|
# If specific tables or regex pattern is specified
|
|
126
|
+
regex_patterns = []
|
|
123
127
|
if self.table_regex:
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
128
|
+
# Split by comma to support multiple regex patterns
|
|
129
|
+
regex_strings = [pattern.strip() for pattern in self.table_regex.split(',')]
|
|
130
|
+
self.logger.debug(f"Parsed regex patterns: {regex_strings}")
|
|
131
|
+
|
|
132
|
+
for regex_str in regex_strings:
|
|
133
|
+
# If the regex looks like a simple path (no regex metacharacters except . and /),
|
|
134
|
+
# escape it to treat it as a literal string
|
|
135
|
+
self.logger.debug(f"Processing regex pattern: {regex_str}")
|
|
136
|
+
# Check if it contains regex metacharacters other than . and /
|
|
137
|
+
import string
|
|
138
|
+
regex_metacharacters = set('[]{}()*+?^$|\\')
|
|
139
|
+
if not any(char in regex_str for char in regex_metacharacters):
|
|
140
|
+
# Escape dots and other special characters for literal matching
|
|
141
|
+
escaped_regex_str = re.escape(regex_str)
|
|
142
|
+
self.logger.debug(f"Treating pattern as literal path, escaped: {escaped_regex_str}")
|
|
143
|
+
regex_patterns.append(re.compile(escaped_regex_str))
|
|
144
|
+
else:
|
|
145
|
+
self.logger.debug(f"Using pattern as regular expression: {regex_str}")
|
|
146
|
+
regex_patterns.append(re.compile(regex_str))
|
|
140
147
|
|
|
141
148
|
def should_process(name):
|
|
142
149
|
if self.tables and name in self.tables:
|
|
143
150
|
self.logger.debug(f"Matched by tables list: {name}")
|
|
144
151
|
return True
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
for pattern in regex_patterns:
|
|
153
|
+
if pattern.fullmatch(name):
|
|
154
|
+
self.logger.debug(f"Matched by regex pattern {pattern.pattern}: {name}")
|
|
155
|
+
return True
|
|
148
156
|
return False
|
|
149
157
|
|
|
150
158
|
def process_item(name, item):
|
|
@@ -184,7 +192,7 @@ class H5Comparator(BaseComparator):
|
|
|
184
192
|
self.logger.error(f"Error processing {table_path}: {str(e)}")
|
|
185
193
|
|
|
186
194
|
# Then process regex pattern if specified
|
|
187
|
-
if
|
|
195
|
+
if regex_patterns:
|
|
188
196
|
def visit_with_regex(name, obj):
|
|
189
197
|
self.logger.debug(f"Checking path: {name}")
|
|
190
198
|
if should_process(name):
|
|
@@ -318,49 +326,60 @@ class H5Comparator(BaseComparator):
|
|
|
318
326
|
|
|
319
327
|
if isinstance(data1, np.ndarray) and isinstance(data2, np.ndarray):
|
|
320
328
|
try:
|
|
329
|
+
# 应用过滤器
|
|
330
|
+
mask1 = self.filter_func(data1) if self.filter_func else np.ones_like(data1, dtype=bool)
|
|
331
|
+
mask2 = self.filter_func(data2) if self.filter_func else np.ones_like(data2, dtype=bool)
|
|
332
|
+
|
|
333
|
+
# 我们只关心两个文件中都满足条件的位置
|
|
334
|
+
combined_mask = mask1 & mask2
|
|
335
|
+
|
|
336
|
+
# 过滤后的数据
|
|
337
|
+
filtered_data1 = data1[combined_mask]
|
|
338
|
+
filtered_data2 = data2[combined_mask]
|
|
339
|
+
|
|
340
|
+
if self.filter_func:
|
|
341
|
+
self.logger.debug(f"Applied filter to {table_name}: {np.sum(combined_mask)}/{data1.size} elements meet criteria")
|
|
342
|
+
|
|
321
343
|
# 对于数值类型数据使用 isclose
|
|
322
344
|
if np.issubdtype(data1.dtype, np.number) and np.issubdtype(data2.dtype, np.number):
|
|
323
|
-
|
|
324
|
-
if not np.all(equal_mask):
|
|
325
|
-
diff_indices = np.where(~equal_mask)
|
|
345
|
+
if not np.all(np.isclose(filtered_data1, filtered_data2, equal_nan=True, rtol=self.rtol, atol=self.atol)):
|
|
326
346
|
if self.show_content_diff:
|
|
327
|
-
#
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
))
|
|
347
|
+
# 如果过滤后数据不相等,需要找到原始数据的索引来报告差异
|
|
348
|
+
# 简化处理:直接报告内容不同
|
|
349
|
+
differences.append(self._create_difference(
|
|
350
|
+
position=table_name,
|
|
351
|
+
expected="Same content (after filtering)",
|
|
352
|
+
actual="Content differs (after filtering)",
|
|
353
|
+
diff_type="content"
|
|
354
|
+
))
|
|
336
355
|
else:
|
|
337
356
|
# Just report that content differs
|
|
338
357
|
differences.append(self._create_difference(
|
|
339
358
|
position=table_name,
|
|
340
|
-
expected="Same content",
|
|
341
|
-
actual="Content differs",
|
|
359
|
+
expected="Same content (after filtering)",
|
|
360
|
+
actual="Content differs (after filtering)",
|
|
342
361
|
diff_type="content"
|
|
343
362
|
))
|
|
344
363
|
identical = False
|
|
345
364
|
# 对于字符串或其他类型直接比较
|
|
346
365
|
else:
|
|
347
|
-
if not np.array_equal(
|
|
366
|
+
if not np.array_equal(filtered_data1, filtered_data2):
|
|
348
367
|
if self.show_content_diff:
|
|
349
368
|
# For non-numeric arrays, find the first difference
|
|
350
|
-
diff_indices = np.where(
|
|
369
|
+
diff_indices = np.where(filtered_data1 != filtered_data2)
|
|
351
370
|
for idx in zip(*diff_indices)[:10]:
|
|
352
371
|
position = f"{table_name}[{','.join(map(str, idx))}]"
|
|
353
372
|
differences.append(self._create_difference(
|
|
354
373
|
position=position,
|
|
355
|
-
expected=str(
|
|
356
|
-
actual=str(
|
|
374
|
+
expected=str(filtered_data1[idx]),
|
|
375
|
+
actual=str(filtered_data2[idx]),
|
|
357
376
|
diff_type="content"
|
|
358
377
|
))
|
|
359
378
|
else:
|
|
360
379
|
differences.append(self._create_difference(
|
|
361
380
|
position=table_name,
|
|
362
|
-
expected="Same content",
|
|
363
|
-
actual="Content differs",
|
|
381
|
+
expected="Same content (after filtering)",
|
|
382
|
+
actual="Content differs (after filtering)",
|
|
364
383
|
diff_type="content"
|
|
365
384
|
))
|
|
366
385
|
identical = False
|
|
@@ -449,6 +468,43 @@ class H5Comparator(BaseComparator):
|
|
|
449
468
|
from .result import Difference
|
|
450
469
|
return Difference(position=position, expected=expected, actual=actual, diff_type=diff_type)
|
|
451
470
|
|
|
471
|
+
def _parse_filter(self):
|
|
472
|
+
"""Parse data filter string and return a filter function"""
|
|
473
|
+
if not self.data_filter:
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
self.logger.debug(f"Parsing data filter: {self.data_filter}")
|
|
477
|
+
try:
|
|
478
|
+
# 匹配模式,例如 'abs>0.1', '>=1e-5', '<-10'
|
|
479
|
+
match = re.match(r"^(abs)?([><]=?|==)([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)$", self.data_filter.replace(" ", ""))
|
|
480
|
+
if not match:
|
|
481
|
+
self.logger.warning(f"Invalid data filter format: {self.data_filter}. Ignoring filter.")
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
use_abs, op, value_str = match.groups()
|
|
485
|
+
value = float(value_str)
|
|
486
|
+
|
|
487
|
+
op_map = {
|
|
488
|
+
'>': np.greater,
|
|
489
|
+
'>=': np.greater_equal,
|
|
490
|
+
'<': np.less,
|
|
491
|
+
'<=': np.less_equal,
|
|
492
|
+
'==': np.equal
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
def filter_func(data):
|
|
496
|
+
if not isinstance(data, np.ndarray) or not np.issubdtype(data.dtype, np.number):
|
|
497
|
+
return np.ones_like(data, dtype=bool) # 对于非数字类型,不过滤
|
|
498
|
+
|
|
499
|
+
target_data = np.abs(data) if use_abs else data
|
|
500
|
+
return op_map[op](target_data, value)
|
|
501
|
+
|
|
502
|
+
self.logger.debug(f"Created filter function for pattern: {use_abs or ''}{op}{value}")
|
|
503
|
+
return filter_func
|
|
504
|
+
except Exception as e:
|
|
505
|
+
self.logger.error(f"Failed to parse data filter '{self.data_filter}': {e}. Ignoring filter.")
|
|
506
|
+
return None
|
|
507
|
+
|
|
452
508
|
# Register the new comparator
|
|
453
509
|
from .factory import ComparatorFactory
|
|
454
510
|
ComparatorFactory.register_comparator('h5', H5Comparator)
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7/src/cli_test_framework.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-test-framework
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
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/ozil111/cli-test-framework
|
|
6
6
|
Author: Xiaotong Wang
|
|
@@ -233,6 +233,11 @@ compare-files data1.h5 data2.h5 \
|
|
|
233
233
|
compare-files data1.h5 data2.h5 \
|
|
234
234
|
--h5-table-regex "group1/.*" \
|
|
235
235
|
--h5-structure-only
|
|
236
|
+
|
|
237
|
+
# Use comma-separated table names with regex (New in 0.3.7)
|
|
238
|
+
compare-files data1.h5 data2.h5 \
|
|
239
|
+
--h5-table-regex "table1,table2,table3" \
|
|
240
|
+
--h5-rtol 1e-6
|
|
236
241
|
```
|
|
237
242
|
|
|
238
243
|
#### Binary Comparison
|
|
@@ -519,3 +524,42 @@ The user manual includes:
|
|
|
519
524
|
**🚀 Ready to supercharge your testing workflow with setup modules, parallel execution and advanced file comparison!**
|
|
520
525
|
|
|
521
526
|
For detailed parallel testing guide, see: [PARALLEL_TESTING_GUIDE.md](https://github.com/ozil111/cli-test-framework/blob/main/PARALLEL_TESTING_GUIDE.md)
|
|
527
|
+
|
|
528
|
+
# 支持数据过滤(New in 0.3.6)
|
|
529
|
+
|
|
530
|
+
你可以通过 `--h5-data-filter` 选项只比较满足特定条件的数据。例如:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# 只比较大于 1e-6 的数据
|
|
534
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '>1e-6'
|
|
535
|
+
|
|
536
|
+
# 只比较绝对值大于 1e-6 的数据
|
|
537
|
+
compare-files data1.h5 data2.h5 --h5-data-filter 'abs>1e-6'
|
|
538
|
+
|
|
539
|
+
# 只比较小于等于 0.01 的数据
|
|
540
|
+
compare-files data1.h5 data2.h5 --h5-data-filter '<=0.01'
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
支持的表达式包括:`>`, `>=`, `<`, `<=`, `==`,以及 `abs` 前缀(绝对值过滤)。
|
|
544
|
+
|
|
545
|
+
# 版本更新日志
|
|
546
|
+
|
|
547
|
+
## 0.3.7 (Latest)
|
|
548
|
+
|
|
549
|
+
### 🐛 Bug Fixes
|
|
550
|
+
- **Fixed H5 table regex matching**: `--h5-table-regex=table1,table2` now correctly matches both `table1` and `table2` instead of treating the entire string as a single regex pattern
|
|
551
|
+
- **Enhanced regex pattern support**: Multiple comma-separated table names are now supported in `--h5-table-regex` parameter
|
|
552
|
+
|
|
553
|
+
### ✨ New Features
|
|
554
|
+
- **Improved HDF5 comparison**: Better handling of multiple table selection with regex patterns
|
|
555
|
+
- **Enhanced debug output**: More detailed logging for HDF5 table matching process
|
|
556
|
+
|
|
557
|
+
### 🔧 Improvements
|
|
558
|
+
- **Backward compatibility**: All existing functionality remains unchanged
|
|
559
|
+
- **Better error handling**: More informative error messages for regex pattern parsing
|
|
560
|
+
|
|
561
|
+
## 0.3.6
|
|
562
|
+
|
|
563
|
+
### ✨ New Features
|
|
564
|
+
- **Data filtering for HDF5 files**: Added `--h5-data-filter` option to compare only data meeting specific criteria
|
|
565
|
+
- **Enhanced HDF5 comparison**: Support for absolute value filtering and various comparison operators
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Demo script to show the effect of different H5 data filters
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import numpy as np
|
|
10
|
+
import tempfile
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
def create_demo_files():
|
|
16
|
+
"""Create demo H5 files with controlled differences"""
|
|
17
|
+
|
|
18
|
+
with tempfile.NamedTemporaryFile(suffix='.h5', delete=False) as f1, \
|
|
19
|
+
tempfile.NamedTemporaryFile(suffix='.h5', delete=False) as f2:
|
|
20
|
+
|
|
21
|
+
file1_path = f1.name
|
|
22
|
+
file2_path = f2.name
|
|
23
|
+
|
|
24
|
+
# Create data with specific differences at different magnitudes
|
|
25
|
+
# Only the largest value (100.0) differs between files
|
|
26
|
+
data1 = np.array([1e-10, 1e-8, 1e-6, 1e-4, 1e-2, 1.0, 10.0, 100.0])
|
|
27
|
+
data2 = np.array([1e-10, 1e-8, 1e-6, 1e-4, 1e-2, 1.0, 10.0, 100.1]) # Only this differs
|
|
28
|
+
|
|
29
|
+
with h5py.File(file1_path, 'w') as f:
|
|
30
|
+
f.create_dataset('demo_data', data=data1)
|
|
31
|
+
|
|
32
|
+
with h5py.File(file2_path, 'w') as f:
|
|
33
|
+
f.create_dataset('demo_data', data=data2)
|
|
34
|
+
|
|
35
|
+
return file1_path, file2_path
|
|
36
|
+
|
|
37
|
+
def run_comparison(file1, file2, filter_expr=None):
|
|
38
|
+
"""Run comparison with optional filter"""
|
|
39
|
+
cmd = [sys.executable, "-m", "src.cli_test_framework.commands.compare", "--file-type", "h5"]
|
|
40
|
+
|
|
41
|
+
if filter_expr:
|
|
42
|
+
cmd.extend(["--h5-data-filter", filter_expr])
|
|
43
|
+
|
|
44
|
+
cmd.extend([file1, file2])
|
|
45
|
+
|
|
46
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
47
|
+
return result.returncode == 0, result.stdout.strip()
|
|
48
|
+
|
|
49
|
+
def demo_filter_effects():
|
|
50
|
+
"""Demonstrate how different filters affect comparison results"""
|
|
51
|
+
|
|
52
|
+
print("=== H5 Data Filter Demo ===\n")
|
|
53
|
+
print("Creating demo files with controlled differences...")
|
|
54
|
+
print("Data: [1e-10, 1e-8, 1e-6, 1e-4, 1e-2, 1.0, 10.0, 100.0] vs [1e-10, 1e-8, 1e-6, 1e-4, 1e-2, 1.0, 10.0, 100.1]")
|
|
55
|
+
print("Only the largest value (100.0 vs 100.1) differs.\n")
|
|
56
|
+
|
|
57
|
+
file1_path, file2_path = create_demo_files()
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Test cases with expected results
|
|
61
|
+
test_cases = [
|
|
62
|
+
("No filter", None, "Should detect difference at 100.0 vs 100.1"),
|
|
63
|
+
("Filter >1e-6", ">1e-6", "Should detect difference (includes 100.0 vs 100.1)"),
|
|
64
|
+
("Filter >1e-4", ">1e-4", "Should detect difference (includes 100.0 vs 100.1)"),
|
|
65
|
+
("Filter >1e-2", ">1e-2", "Should detect difference (includes 100.0 vs 100.1)"),
|
|
66
|
+
("Filter >1.0", ">1.0", "Should detect difference (includes 100.0 vs 100.1)"),
|
|
67
|
+
("Filter >10.0", ">10.0", "Should detect difference (includes 100.0 vs 100.1)"),
|
|
68
|
+
("Filter >100.0", ">100.0", "Should NOT detect difference (neither 100.0 nor 100.1 > 100.0)"),
|
|
69
|
+
("Filter >=100.0", ">=100.0", "Should detect difference (both >= 100.0, but 100.0 != 100.1)"),
|
|
70
|
+
("Filter <1e-6", "<1e-6", "Should NOT detect difference (no values < 1e-6 differ)"),
|
|
71
|
+
("Filter <=1e-2", "<=1e-2", "Should NOT detect difference (no values <= 1e-2 differ)"),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
print("Filter Test Results:")
|
|
75
|
+
print("-" * 80)
|
|
76
|
+
print(f"{'Filter':<20} {'Result':<10} {'Expected':<15} {'Status':<10}")
|
|
77
|
+
print("-" * 80)
|
|
78
|
+
|
|
79
|
+
for filter_name, filter_expr, expected in test_cases:
|
|
80
|
+
identical, output = run_comparison(file1_path, file2_path, filter_expr)
|
|
81
|
+
|
|
82
|
+
# Determine if result matches expectation
|
|
83
|
+
if "100.0 vs 100.1" in expected:
|
|
84
|
+
expected_identical = False
|
|
85
|
+
else:
|
|
86
|
+
expected_identical = True
|
|
87
|
+
|
|
88
|
+
status = "✓ PASS" if identical == expected_identical else "✗ FAIL"
|
|
89
|
+
|
|
90
|
+
print(f"{filter_name:<20} {'Identical' if identical else 'Different':<10} {expected_identical:<15} {status:<10}")
|
|
91
|
+
|
|
92
|
+
if not identical and "NOT" in expected:
|
|
93
|
+
print(f" → Unexpected: Found difference when none expected")
|
|
94
|
+
elif identical and "NOT" not in expected:
|
|
95
|
+
print(f" → Unexpected: No difference found when one expected")
|
|
96
|
+
|
|
97
|
+
finally:
|
|
98
|
+
os.unlink(file1_path)
|
|
99
|
+
os.unlink(file2_path)
|
|
100
|
+
print("\nDemo files cleaned up.")
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
demo_filter_effects()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/commands/__init__.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/__init__.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/assertions.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/base_runner.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/parallel_runner.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/process_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/core/test_case.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
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/__init__.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/json_runner.py
RENAMED
|
File without changes
|
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/runners/yaml_runner.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/utils/__init__.py
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework/utils/path_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/requires.txt
RENAMED
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/src/cli_test_framework.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{cli_test_framework-0.3.5 → cli_test_framework-0.3.7}/tests/__pycache__/__init__.cpython-312.pyc
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
|
|
File without changes
|