pytest-dsl 0.14.0__tar.gz → 0.15.1__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.
- {pytest_dsl-0.14.0/pytest_dsl.egg-info → pytest_dsl-0.15.1}/PKG-INFO +1 -1
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pyproject.toml +4 -4
- pytest_dsl-0.15.1/pytest_dsl/__init__.py +209 -0
- pytest_dsl-0.15.1/pytest_dsl/cli.py +371 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/dsl_executor.py +393 -183
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/dsl_executor_utils.py +21 -22
- pytest_dsl-0.15.1/pytest_dsl/core/execution_tracker.py +291 -0
- pytest_dsl-0.15.1/pytest_dsl/core/keyword_loader.py +402 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/keyword_manager.py +29 -23
- pytest_dsl-0.15.1/pytest_dsl/core/keyword_utils.py +609 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/lexer.py +8 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/parser.py +130 -20
- pytest_dsl-0.15.1/pytest_dsl/core/validator.py +417 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/plugin.py +10 -1
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1/pytest_dsl.egg-info}/PKG-INFO +1 -1
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl.egg-info/SOURCES.txt +4 -0
- pytest_dsl-0.14.0/pytest_dsl/__init__.py +0 -10
- pytest_dsl-0.14.0/pytest_dsl/cli.py +0 -1038
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/LICENSE +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/MANIFEST.in +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/README.md +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/conftest_adapter.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/__init__.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/auth_provider.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/auto_decorator.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/auto_directory.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/context.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/custom_keyword_manager.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/global_context.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/hook_manager.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/hookable_executor.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/hookable_keyword_manager.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/hookspecs.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/http_client.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/http_request.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/parsetab.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/plugin_discovery.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/utils.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/variable_utils.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/yaml_loader.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/core/yaml_vars.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/docs/custom_keywords.md +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/__init__.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/assert/expression_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/__init__.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_advanced.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_example.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_length_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_retry_assertions.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/new_retry_test.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/retry_assertions_only.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/retry_config_only.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/retry_debug.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/retry_with_fix.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/simple_retry.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/http/vars.yaml +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/quickstart/loops.auto +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/test_assert.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/test_custom_keyword.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/test_http.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/examples/test_quickstart.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/keywords/__init__.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/keywords/assertion_keywords.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/keywords/global_keywords.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/keywords/http_keywords.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/keywords/system_keywords.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/main_adapter.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/remote/__init__.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/remote/hook_manager.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/remote/keyword_client.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/remote/keyword_server.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/remote/variable_bridge.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl/templates/keywords_report.html +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl.egg-info/dependency_links.txt +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl.egg-info/entry_points.txt +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl.egg-info/requires.txt +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/pytest_dsl.egg-info/top_level.txt +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/setup.cfg +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/setup.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/auth_config.yaml +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/mock_config.yaml +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/simple_config.yaml +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_auth_mock_server.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_auth_runner.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_end_to_end_seamless.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_enhanced_variable_access.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_http_assertions_extractors.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_mock_server.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_platform_hook_integration.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_platform_hook_pytest.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_retry_runner.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_seamless_variable_sync.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_simple_hook_demo.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_variable_sync.py +0 -0
- {pytest_dsl-0.14.0 → pytest_dsl-0.15.1}/tests/test_variable_sync_demo.py +0 -0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "pytest-dsl"
|
7
|
-
version = "0.
|
7
|
+
version = "0.15.1"
|
8
8
|
description = "A DSL testing framework based on pytest"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.9"
|
@@ -49,6 +49,6 @@ pytest-dsl-list = "pytest_dsl.cli:main_list_keywords"
|
|
49
49
|
"Bug Tracker" = "https://github.com/felix-1991/pytest-dsl/issues"
|
50
50
|
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
[[tool.uv.index]]
|
53
|
+
name = "tuna"
|
54
|
+
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
@@ -0,0 +1,209 @@
|
|
1
|
+
"""
|
2
|
+
pytest-dsl - 基于pytest的DSL测试框架
|
3
|
+
|
4
|
+
使用自定义的领域特定语言(DSL)来编写测试用例,使测试更加直观、易读和易维护。
|
5
|
+
|
6
|
+
主要功能:
|
7
|
+
- DSL语法解析和执行
|
8
|
+
- 关键字管理和注册
|
9
|
+
- Hook机制支持插件扩展
|
10
|
+
- DSL格式校验
|
11
|
+
- 远程关键字支持
|
12
|
+
- 自定义关键字支持
|
13
|
+
"""
|
14
|
+
|
15
|
+
__version__ = "0.1.0"
|
16
|
+
|
17
|
+
# 核心执行器
|
18
|
+
from pytest_dsl.core.dsl_executor import DSLExecutor
|
19
|
+
|
20
|
+
# 关键字管理器
|
21
|
+
from pytest_dsl.core.keyword_manager import keyword_manager, KeywordManager
|
22
|
+
|
23
|
+
# Hook系统
|
24
|
+
from pytest_dsl.core.hookspecs import hookimpl, hookspec, DSLHookSpecs
|
25
|
+
from pytest_dsl.core.hook_manager import hook_manager, DSLHookManager
|
26
|
+
|
27
|
+
# DSL格式校验
|
28
|
+
from pytest_dsl.core.validator import (
|
29
|
+
DSLValidator,
|
30
|
+
DSLValidationError,
|
31
|
+
validate_dsl,
|
32
|
+
check_dsl_syntax
|
33
|
+
)
|
34
|
+
|
35
|
+
# 自动装饰器
|
36
|
+
from pytest_dsl.core.auto_decorator import auto_dsl
|
37
|
+
|
38
|
+
# 核心工具类
|
39
|
+
from pytest_dsl.core.parser import Node, get_parser
|
40
|
+
from pytest_dsl.core.lexer import get_lexer
|
41
|
+
from pytest_dsl.core.context import TestContext
|
42
|
+
from pytest_dsl.core.global_context import global_context
|
43
|
+
|
44
|
+
# 变量工具
|
45
|
+
from pytest_dsl.core.variable_utils import VariableReplacer
|
46
|
+
|
47
|
+
# 自定义关键字管理器
|
48
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
49
|
+
|
50
|
+
# 关键字加载器
|
51
|
+
from pytest_dsl.core.keyword_loader import (
|
52
|
+
keyword_loader, KeywordLoader,
|
53
|
+
load_all_keywords, categorize_keyword, get_keyword_source_info,
|
54
|
+
group_keywords_by_source, scan_project_custom_keywords
|
55
|
+
)
|
56
|
+
|
57
|
+
# 关键字工具
|
58
|
+
from pytest_dsl.core.keyword_utils import (
|
59
|
+
KeywordInfo, KeywordListOptions, KeywordFormatter, KeywordLister,
|
60
|
+
keyword_lister, list_keywords, get_keyword_info, search_keywords,
|
61
|
+
generate_html_report
|
62
|
+
)
|
63
|
+
|
64
|
+
# 便捷导入的别名
|
65
|
+
Executor = DSLExecutor
|
66
|
+
Validator = DSLValidator
|
67
|
+
HookManager = DSLHookManager
|
68
|
+
KeywordLoader = KeywordLoader
|
69
|
+
|
70
|
+
# 导出所有公共接口
|
71
|
+
__all__ = [
|
72
|
+
# 版本信息
|
73
|
+
'__version__',
|
74
|
+
|
75
|
+
# 核心执行器
|
76
|
+
'DSLExecutor', 'Executor',
|
77
|
+
|
78
|
+
# 关键字管理
|
79
|
+
'keyword_manager', 'KeywordManager',
|
80
|
+
'custom_keyword_manager',
|
81
|
+
|
82
|
+
# 关键字加载器
|
83
|
+
'keyword_loader', 'KeywordLoader',
|
84
|
+
'load_all_keywords', 'categorize_keyword', 'get_keyword_source_info',
|
85
|
+
'group_keywords_by_source', 'scan_project_custom_keywords',
|
86
|
+
|
87
|
+
# 关键字工具
|
88
|
+
'KeywordInfo', 'KeywordListOptions', 'KeywordFormatter', 'KeywordLister',
|
89
|
+
'keyword_lister', 'list_keywords', 'get_keyword_info', 'search_keywords',
|
90
|
+
'generate_html_report',
|
91
|
+
|
92
|
+
# Hook系统
|
93
|
+
'hookimpl', 'hookspec', 'DSLHookSpecs',
|
94
|
+
'hook_manager', 'DSLHookManager', 'HookManager',
|
95
|
+
|
96
|
+
# DSL校验
|
97
|
+
'DSLValidator', 'Validator',
|
98
|
+
'DSLValidationError',
|
99
|
+
'validate_dsl',
|
100
|
+
'check_dsl_syntax',
|
101
|
+
|
102
|
+
# 自动装饰器
|
103
|
+
'auto_dsl',
|
104
|
+
|
105
|
+
# 核心组件
|
106
|
+
'Node', 'get_parser', 'get_lexer',
|
107
|
+
'TestContext', 'global_context',
|
108
|
+
'VariableReplacer',
|
109
|
+
]
|
110
|
+
|
111
|
+
# 快捷函数
|
112
|
+
|
113
|
+
|
114
|
+
def create_executor(enable_hooks: bool = True) -> DSLExecutor:
|
115
|
+
"""创建DSL执行器实例
|
116
|
+
|
117
|
+
Args:
|
118
|
+
enable_hooks: 是否启用hook机制
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
DSL执行器实例
|
122
|
+
"""
|
123
|
+
return DSLExecutor(enable_hooks=enable_hooks)
|
124
|
+
|
125
|
+
|
126
|
+
def parse_dsl(content: str) -> Node:
|
127
|
+
"""解析DSL内容为AST
|
128
|
+
|
129
|
+
Args:
|
130
|
+
content: DSL内容
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
解析后的AST根节点
|
134
|
+
"""
|
135
|
+
lexer = get_lexer()
|
136
|
+
parser = get_parser()
|
137
|
+
return parser.parse(content, lexer=lexer)
|
138
|
+
|
139
|
+
|
140
|
+
def execute_dsl(content: str, context: dict = None, enable_hooks: bool = True) -> any:
|
141
|
+
"""执行DSL内容的便捷函数
|
142
|
+
|
143
|
+
Args:
|
144
|
+
content: DSL内容
|
145
|
+
context: 执行上下文(可选)
|
146
|
+
enable_hooks: 是否启用hook机制
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
执行结果
|
150
|
+
"""
|
151
|
+
executor = create_executor(enable_hooks=enable_hooks)
|
152
|
+
if context:
|
153
|
+
executor.variables.update(context)
|
154
|
+
for key, value in context.items():
|
155
|
+
executor.test_context.set(key, value)
|
156
|
+
|
157
|
+
ast = parse_dsl(content)
|
158
|
+
return executor.execute(ast)
|
159
|
+
|
160
|
+
|
161
|
+
def register_keyword(name: str, parameters: list = None, source_type: str = "external",
|
162
|
+
source_name: str = "user_defined"):
|
163
|
+
"""注册关键字的装饰器
|
164
|
+
|
165
|
+
Args:
|
166
|
+
name: 关键字名称
|
167
|
+
parameters: 参数列表
|
168
|
+
source_type: 来源类型
|
169
|
+
source_name: 来源名称
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
装饰器函数
|
173
|
+
"""
|
174
|
+
if parameters is None:
|
175
|
+
parameters = []
|
176
|
+
|
177
|
+
return keyword_manager.register_with_source(
|
178
|
+
name=name,
|
179
|
+
parameters=parameters,
|
180
|
+
source_type=source_type,
|
181
|
+
source_name=source_name
|
182
|
+
)
|
183
|
+
|
184
|
+
|
185
|
+
# 版本兼容性检查
|
186
|
+
def check_version_compatibility():
|
187
|
+
"""检查版本兼容性"""
|
188
|
+
try:
|
189
|
+
import sys
|
190
|
+
if sys.version_info < (3, 7):
|
191
|
+
import warnings
|
192
|
+
warnings.warn(
|
193
|
+
"pytest-dsl 需要 Python 3.7 或更高版本",
|
194
|
+
UserWarning,
|
195
|
+
stacklevel=2
|
196
|
+
)
|
197
|
+
except Exception:
|
198
|
+
pass
|
199
|
+
|
200
|
+
|
201
|
+
# 初始化时进行版本检查
|
202
|
+
check_version_compatibility()
|
203
|
+
|
204
|
+
# 自动初始化hook管理器
|
205
|
+
try:
|
206
|
+
hook_manager.initialize()
|
207
|
+
except Exception as e:
|
208
|
+
import warnings
|
209
|
+
warnings.warn(f"Hook管理器初始化失败: {e}", UserWarning, stacklevel=2)
|
@@ -0,0 +1,371 @@
|
|
1
|
+
"""
|
2
|
+
pytest-dsl命令行入口
|
3
|
+
|
4
|
+
提供独立的命令行工具,用于执行DSL文件。
|
5
|
+
"""
|
6
|
+
|
7
|
+
import sys
|
8
|
+
import argparse
|
9
|
+
import os
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
from pytest_dsl.core.lexer import get_lexer
|
13
|
+
from pytest_dsl.core.parser import get_parser
|
14
|
+
from pytest_dsl.core.dsl_executor import DSLExecutor
|
15
|
+
from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
|
16
|
+
from pytest_dsl.core.auto_directory import (
|
17
|
+
SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
|
18
|
+
)
|
19
|
+
from pytest_dsl.core.keyword_loader import load_all_keywords
|
20
|
+
from pytest_dsl.core.keyword_utils import list_keywords as utils_list_keywords
|
21
|
+
|
22
|
+
|
23
|
+
def read_file(filename):
|
24
|
+
"""读取 DSL 文件内容"""
|
25
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
26
|
+
return f.read()
|
27
|
+
|
28
|
+
|
29
|
+
def parse_args():
|
30
|
+
"""解析命令行参数"""
|
31
|
+
import sys
|
32
|
+
argv = sys.argv[1:] # 去掉脚本名
|
33
|
+
|
34
|
+
# 检查是否使用了子命令格式
|
35
|
+
if argv and argv[0] in ['run', 'list-keywords']:
|
36
|
+
# 使用新的子命令格式
|
37
|
+
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
38
|
+
subparsers = parser.add_subparsers(dest='command', help='可用命令')
|
39
|
+
|
40
|
+
# 执行命令
|
41
|
+
run_parser = subparsers.add_parser('run', help='执行DSL文件')
|
42
|
+
run_parser.add_argument(
|
43
|
+
'path',
|
44
|
+
help='要执行的DSL文件路径或包含DSL文件的目录'
|
45
|
+
)
|
46
|
+
run_parser.add_argument(
|
47
|
+
'--yaml-vars', action='append', default=[],
|
48
|
+
help='YAML变量文件路径,可以指定多个文件 '
|
49
|
+
'(例如: --yaml-vars vars1.yaml '
|
50
|
+
'--yaml-vars vars2.yaml)'
|
51
|
+
)
|
52
|
+
run_parser.add_argument(
|
53
|
+
'--yaml-vars-dir', default=None,
|
54
|
+
help='YAML变量文件目录路径,'
|
55
|
+
'将加载该目录下所有.yaml文件'
|
56
|
+
)
|
57
|
+
|
58
|
+
# 关键字列表命令
|
59
|
+
list_parser = subparsers.add_parser(
|
60
|
+
'list-keywords',
|
61
|
+
help='罗列所有可用关键字和参数信息'
|
62
|
+
)
|
63
|
+
list_parser.add_argument(
|
64
|
+
'--format', choices=['text', 'json', 'html'],
|
65
|
+
default='json',
|
66
|
+
help='输出格式:json(默认)、text 或 html'
|
67
|
+
)
|
68
|
+
list_parser.add_argument(
|
69
|
+
'--output', '-o', type=str, default=None,
|
70
|
+
help='输出文件路径(json格式默认为keywords.json,html格式默认为keywords.html)'
|
71
|
+
)
|
72
|
+
list_parser.add_argument(
|
73
|
+
'--filter', type=str, default=None,
|
74
|
+
help='过滤关键字名称(支持部分匹配)'
|
75
|
+
)
|
76
|
+
list_parser.add_argument(
|
77
|
+
'--category',
|
78
|
+
choices=[
|
79
|
+
'builtin', 'plugin', 'custom',
|
80
|
+
'project_custom', 'remote', 'all'
|
81
|
+
],
|
82
|
+
default='all',
|
83
|
+
help='关键字类别:builtin(内置)、plugin(插件)、custom(自定义)、'
|
84
|
+
'project_custom(项目自定义)、remote(远程)、all(全部,默认)'
|
85
|
+
)
|
86
|
+
list_parser.add_argument(
|
87
|
+
'--include-remote', action='store_true',
|
88
|
+
help='是否包含远程关键字(默认不包含)'
|
89
|
+
)
|
90
|
+
|
91
|
+
return parser.parse_args(argv)
|
92
|
+
else:
|
93
|
+
# 向后兼容模式
|
94
|
+
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
95
|
+
|
96
|
+
# 检查是否是list-keywords的旧格式
|
97
|
+
if '--list-keywords' in argv:
|
98
|
+
parser.add_argument('--list-keywords', action='store_true')
|
99
|
+
parser.add_argument(
|
100
|
+
'--format', choices=['text', 'json', 'html'], default='json'
|
101
|
+
)
|
102
|
+
parser.add_argument(
|
103
|
+
'--output', '-o', type=str, default=None
|
104
|
+
)
|
105
|
+
parser.add_argument('--filter', type=str, default=None)
|
106
|
+
parser.add_argument(
|
107
|
+
'--category',
|
108
|
+
choices=[
|
109
|
+
'builtin', 'plugin', 'custom',
|
110
|
+
'project_custom', 'remote', 'all'
|
111
|
+
],
|
112
|
+
default='all'
|
113
|
+
)
|
114
|
+
parser.add_argument(
|
115
|
+
'--include-remote', action='store_true'
|
116
|
+
)
|
117
|
+
parser.add_argument('path', nargs='?') # 可选的路径参数
|
118
|
+
parser.add_argument(
|
119
|
+
'--yaml-vars', action='append', default=[]
|
120
|
+
)
|
121
|
+
parser.add_argument('--yaml-vars-dir', default=None)
|
122
|
+
|
123
|
+
args = parser.parse_args(argv)
|
124
|
+
args.command = 'list-keywords-compat' # 标记为兼容模式
|
125
|
+
else:
|
126
|
+
# 默认为run命令的向后兼容模式
|
127
|
+
parser.add_argument('path', nargs='?')
|
128
|
+
parser.add_argument(
|
129
|
+
'--yaml-vars', action='append', default=[]
|
130
|
+
)
|
131
|
+
parser.add_argument('--yaml-vars-dir', default=None)
|
132
|
+
|
133
|
+
args = parser.parse_args(argv)
|
134
|
+
args.command = 'run-compat' # 标记为兼容模式
|
135
|
+
|
136
|
+
return args
|
137
|
+
|
138
|
+
|
139
|
+
def list_keywords(output_format='json', name_filter=None,
|
140
|
+
category_filter='all', output_file=None,
|
141
|
+
include_remote=False):
|
142
|
+
"""罗列所有关键字信息(简化版,调用统一的工具函数)"""
|
143
|
+
print("正在加载关键字...")
|
144
|
+
|
145
|
+
# 使用统一的工具函数
|
146
|
+
try:
|
147
|
+
utils_list_keywords(
|
148
|
+
output_format=output_format,
|
149
|
+
name_filter=name_filter,
|
150
|
+
category_filter=category_filter,
|
151
|
+
include_remote=include_remote,
|
152
|
+
output_file=output_file,
|
153
|
+
print_summary=True
|
154
|
+
)
|
155
|
+
except Exception as e:
|
156
|
+
print(f"列出关键字失败: {e}")
|
157
|
+
raise
|
158
|
+
|
159
|
+
|
160
|
+
def load_yaml_variables(args):
|
161
|
+
"""从命令行参数加载YAML变量"""
|
162
|
+
# 使用统一的加载函数,包含远程服务器自动连接功能和hook支持
|
163
|
+
try:
|
164
|
+
# 尝试从环境变量获取环境名称
|
165
|
+
environment = (os.environ.get('PYTEST_DSL_ENVIRONMENT') or
|
166
|
+
os.environ.get('ENVIRONMENT'))
|
167
|
+
|
168
|
+
load_yaml_variables_from_args(
|
169
|
+
yaml_files=args.yaml_vars,
|
170
|
+
yaml_vars_dir=args.yaml_vars_dir,
|
171
|
+
project_root=os.getcwd(), # CLI模式下使用当前工作目录作为项目根目录
|
172
|
+
environment=environment
|
173
|
+
)
|
174
|
+
except Exception as e:
|
175
|
+
print(f"加载YAML变量失败: {str(e)}")
|
176
|
+
sys.exit(1)
|
177
|
+
|
178
|
+
|
179
|
+
def execute_dsl_file(file_path, lexer, parser, executor):
|
180
|
+
"""执行单个DSL文件"""
|
181
|
+
try:
|
182
|
+
print(f"执行文件: {file_path}")
|
183
|
+
dsl_code = read_file(file_path)
|
184
|
+
ast = parser.parse(dsl_code, lexer=lexer)
|
185
|
+
executor.execute(ast)
|
186
|
+
return True
|
187
|
+
except Exception as e:
|
188
|
+
print(f"执行失败 {file_path}: {e}")
|
189
|
+
return False
|
190
|
+
|
191
|
+
|
192
|
+
def find_dsl_files(directory):
|
193
|
+
"""查找目录中的所有DSL文件"""
|
194
|
+
dsl_files = []
|
195
|
+
for root, _, files in os.walk(directory):
|
196
|
+
for file in files:
|
197
|
+
if (file.endswith(('.dsl', '.auto')) and
|
198
|
+
file not in [SETUP_FILE_NAME, TEARDOWN_FILE_NAME]):
|
199
|
+
dsl_files.append(os.path.join(root, file))
|
200
|
+
return dsl_files
|
201
|
+
|
202
|
+
|
203
|
+
def run_dsl_tests(args):
|
204
|
+
"""执行DSL测试的主函数"""
|
205
|
+
path = args.path
|
206
|
+
|
207
|
+
if not path:
|
208
|
+
print("错误: 必须指定要执行的DSL文件路径或目录")
|
209
|
+
sys.exit(1)
|
210
|
+
|
211
|
+
# 加载内置关键字插件(运行时总是包含远程关键字)
|
212
|
+
load_all_keywords(include_remote=True)
|
213
|
+
|
214
|
+
# 加载YAML变量(包括远程服务器自动连接)
|
215
|
+
load_yaml_variables(args)
|
216
|
+
|
217
|
+
# 支持hook机制的执行
|
218
|
+
from pytest_dsl.core.hookable_executor import hookable_executor
|
219
|
+
|
220
|
+
# 检查是否有hook提供的用例列表
|
221
|
+
hook_cases = hookable_executor.list_dsl_cases()
|
222
|
+
if hook_cases:
|
223
|
+
# 如果有hook提供的用例,优先执行这些用例
|
224
|
+
print(f"通过Hook发现 {len(hook_cases)} 个DSL用例")
|
225
|
+
failures = 0
|
226
|
+
for case in hook_cases:
|
227
|
+
case_id = case.get('id') or case.get('name', 'unknown')
|
228
|
+
try:
|
229
|
+
print(f"执行用例: {case.get('name', case_id)}")
|
230
|
+
hookable_executor.execute_dsl(str(case_id))
|
231
|
+
print(f"✓ 用例 {case.get('name', case_id)} 执行成功")
|
232
|
+
except Exception as e:
|
233
|
+
print(f"✗ 用例 {case.get('name', case_id)} 执行失败: {e}")
|
234
|
+
failures += 1
|
235
|
+
|
236
|
+
if failures > 0:
|
237
|
+
print(f"总计 {failures}/{len(hook_cases)} 个测试失败")
|
238
|
+
sys.exit(1)
|
239
|
+
else:
|
240
|
+
print(f"所有 {len(hook_cases)} 个测试成功完成")
|
241
|
+
return
|
242
|
+
|
243
|
+
# 如果没有hook用例,使用传统的文件执行方式
|
244
|
+
lexer = get_lexer()
|
245
|
+
parser = get_parser()
|
246
|
+
executor = DSLExecutor()
|
247
|
+
|
248
|
+
# 检查路径是文件还是目录
|
249
|
+
if os.path.isfile(path):
|
250
|
+
# 执行单个文件
|
251
|
+
success = execute_dsl_file(path, lexer, parser, executor)
|
252
|
+
if not success:
|
253
|
+
sys.exit(1)
|
254
|
+
elif os.path.isdir(path):
|
255
|
+
# 执行目录中的所有DSL文件
|
256
|
+
print(f"执行目录: {path}")
|
257
|
+
|
258
|
+
# 先执行目录的setup文件(如果存在)
|
259
|
+
setup_file = os.path.join(path, SETUP_FILE_NAME)
|
260
|
+
if os.path.exists(setup_file):
|
261
|
+
execute_hook_file(Path(setup_file), True, path)
|
262
|
+
|
263
|
+
# 查找并执行所有DSL文件
|
264
|
+
dsl_files = find_dsl_files(path)
|
265
|
+
if not dsl_files:
|
266
|
+
print(f"目录中没有找到DSL文件: {path}")
|
267
|
+
sys.exit(1)
|
268
|
+
|
269
|
+
print(f"找到 {len(dsl_files)} 个DSL文件")
|
270
|
+
|
271
|
+
# 执行所有DSL文件
|
272
|
+
failures = 0
|
273
|
+
for file_path in dsl_files:
|
274
|
+
success = execute_dsl_file(file_path, lexer, parser, executor)
|
275
|
+
if not success:
|
276
|
+
failures += 1
|
277
|
+
|
278
|
+
# 最后执行目录的teardown文件(如果存在)
|
279
|
+
teardown_file = os.path.join(path, TEARDOWN_FILE_NAME)
|
280
|
+
if os.path.exists(teardown_file):
|
281
|
+
execute_hook_file(Path(teardown_file), False, path)
|
282
|
+
|
283
|
+
# 如果有失败的测试,返回非零退出码
|
284
|
+
if failures > 0:
|
285
|
+
print(f"总计 {failures}/{len(dsl_files)} 个测试失败")
|
286
|
+
sys.exit(1)
|
287
|
+
else:
|
288
|
+
print(f"所有 {len(dsl_files)} 个测试成功完成")
|
289
|
+
else:
|
290
|
+
print(f"路径不存在: {path}")
|
291
|
+
sys.exit(1)
|
292
|
+
|
293
|
+
|
294
|
+
def main():
|
295
|
+
"""命令行入口点"""
|
296
|
+
args = parse_args()
|
297
|
+
|
298
|
+
# 处理子命令
|
299
|
+
if args.command == 'list-keywords':
|
300
|
+
list_keywords(
|
301
|
+
output_format=args.format,
|
302
|
+
name_filter=args.filter,
|
303
|
+
category_filter=args.category,
|
304
|
+
output_file=args.output,
|
305
|
+
include_remote=args.include_remote
|
306
|
+
)
|
307
|
+
elif args.command == 'run':
|
308
|
+
run_dsl_tests(args)
|
309
|
+
elif args.command == 'list-keywords-compat':
|
310
|
+
# 向后兼容:旧的--list-keywords格式
|
311
|
+
output_file = getattr(args, 'output', None)
|
312
|
+
include_remote = getattr(args, 'include_remote', False)
|
313
|
+
list_keywords(
|
314
|
+
output_format=args.format,
|
315
|
+
name_filter=args.filter,
|
316
|
+
category_filter=args.category,
|
317
|
+
output_file=output_file,
|
318
|
+
include_remote=include_remote
|
319
|
+
)
|
320
|
+
elif args.command == 'run-compat':
|
321
|
+
# 向后兼容:默认执行DSL测试
|
322
|
+
run_dsl_tests(args)
|
323
|
+
else:
|
324
|
+
# 如果没有匹配的命令,显示帮助
|
325
|
+
print("错误: 未知命令")
|
326
|
+
sys.exit(1)
|
327
|
+
|
328
|
+
|
329
|
+
def main_list_keywords():
|
330
|
+
"""关键字列表命令的专用入口点"""
|
331
|
+
parser = argparse.ArgumentParser(description='查看pytest-dsl可用关键字列表')
|
332
|
+
parser.add_argument(
|
333
|
+
'--format', choices=['text', 'json', 'html'],
|
334
|
+
default='json',
|
335
|
+
help='输出格式:json(默认)、text 或 html'
|
336
|
+
)
|
337
|
+
parser.add_argument(
|
338
|
+
'--output', '-o', type=str, default=None,
|
339
|
+
help='输出文件路径(json格式默认为keywords.json,html格式默认为keywords.html)'
|
340
|
+
)
|
341
|
+
parser.add_argument(
|
342
|
+
'--filter', type=str, default=None,
|
343
|
+
help='过滤关键字名称(支持部分匹配)'
|
344
|
+
)
|
345
|
+
parser.add_argument(
|
346
|
+
'--category',
|
347
|
+
choices=[
|
348
|
+
'builtin', 'plugin', 'custom', 'project_custom', 'remote', 'all'
|
349
|
+
],
|
350
|
+
default='all',
|
351
|
+
help='关键字类别:builtin(内置)、plugin(插件)、custom(自定义)、'
|
352
|
+
'project_custom(项目自定义)、remote(远程)、all(全部,默认)'
|
353
|
+
)
|
354
|
+
parser.add_argument(
|
355
|
+
'--include-remote', action='store_true',
|
356
|
+
help='是否包含远程关键字(默认不包含)'
|
357
|
+
)
|
358
|
+
|
359
|
+
args = parser.parse_args()
|
360
|
+
|
361
|
+
list_keywords(
|
362
|
+
output_format=args.format,
|
363
|
+
name_filter=args.filter,
|
364
|
+
category_filter=args.category,
|
365
|
+
output_file=args.output,
|
366
|
+
include_remote=args.include_remote
|
367
|
+
)
|
368
|
+
|
369
|
+
|
370
|
+
if __name__ == '__main__':
|
371
|
+
main()
|