pytest-dsl 0.13.0__tar.gz → 0.15.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 (106) hide show
  1. {pytest_dsl-0.13.0/pytest_dsl.egg-info → pytest_dsl-0.15.0}/PKG-INFO +1 -1
  2. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pyproject.toml +1 -1
  3. pytest_dsl-0.15.0/pytest_dsl/__init__.py +197 -0
  4. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/cli.py +37 -260
  5. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/custom_keyword_manager.py +114 -14
  6. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/dsl_executor.py +549 -166
  7. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/dsl_executor_utils.py +21 -22
  8. pytest_dsl-0.15.0/pytest_dsl/core/execution_tracker.py +291 -0
  9. pytest_dsl-0.15.0/pytest_dsl/core/hook_manager.py +87 -0
  10. pytest_dsl-0.15.0/pytest_dsl/core/hookable_executor.py +134 -0
  11. pytest_dsl-0.15.0/pytest_dsl/core/hookable_keyword_manager.py +106 -0
  12. pytest_dsl-0.15.0/pytest_dsl/core/hookspecs.py +175 -0
  13. pytest_dsl-0.15.0/pytest_dsl/core/keyword_loader.py +402 -0
  14. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/keyword_manager.py +29 -23
  15. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/parser.py +94 -18
  16. pytest_dsl-0.15.0/pytest_dsl/core/validator.py +417 -0
  17. pytest_dsl-0.15.0/pytest_dsl/core/yaml_loader.py +239 -0
  18. pytest_dsl-0.15.0/pytest_dsl/core/yaml_vars.py +158 -0
  19. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/plugin.py +10 -1
  20. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0/pytest_dsl.egg-info}/PKG-INFO +1 -1
  21. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl.egg-info/SOURCES.txt +14 -0
  22. pytest_dsl-0.15.0/tests/auth_config.yaml +280 -0
  23. pytest_dsl-0.15.0/tests/simple_config.yaml +64 -0
  24. pytest_dsl-0.15.0/tests/test_auth_mock_server.py +627 -0
  25. pytest_dsl-0.15.0/tests/test_auth_runner.py +337 -0
  26. pytest_dsl-0.15.0/tests/test_platform_hook_integration.py +981 -0
  27. pytest_dsl-0.15.0/tests/test_platform_hook_pytest.py +59 -0
  28. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_retry_runner.py +0 -1
  29. pytest_dsl-0.15.0/tests/test_simple_hook_demo.py +357 -0
  30. pytest_dsl-0.13.0/pytest_dsl/__init__.py +0 -10
  31. pytest_dsl-0.13.0/pytest_dsl/core/yaml_loader.py +0 -139
  32. pytest_dsl-0.13.0/pytest_dsl/core/yaml_vars.py +0 -75
  33. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/LICENSE +0 -0
  34. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/MANIFEST.in +0 -0
  35. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/README.md +0 -0
  36. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/conftest_adapter.py +0 -0
  37. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/__init__.py +0 -0
  38. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/auth_provider.py +0 -0
  39. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/auto_decorator.py +0 -0
  40. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/auto_directory.py +0 -0
  41. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/context.py +0 -0
  42. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/global_context.py +0 -0
  43. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/http_client.py +0 -0
  44. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/http_request.py +0 -0
  45. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/lexer.py +0 -0
  46. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/parsetab.py +0 -0
  47. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/plugin_discovery.py +0 -0
  48. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/utils.py +0 -0
  49. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/core/variable_utils.py +0 -0
  50. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/docs/custom_keywords.md +0 -0
  51. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/__init__.py +0 -0
  52. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
  53. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
  54. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/assert/expression_test.auto +0 -0
  55. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
  56. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
  57. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
  58. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/__init__.py +0 -0
  59. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
  60. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
  61. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_advanced.auto +0 -0
  62. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_example.auto +0 -0
  63. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_length_test.auto +0 -0
  64. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_retry_assertions.auto +0 -0
  65. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +0 -0
  66. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
  67. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/new_retry_test.auto +0 -0
  68. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/retry_assertions_only.auto +0 -0
  69. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/retry_config_only.auto +0 -0
  70. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/retry_debug.auto +0 -0
  71. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/retry_with_fix.auto +0 -0
  72. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/simple_retry.auto +0 -0
  73. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/http/vars.yaml +0 -0
  74. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
  75. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
  76. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/quickstart/loops.auto +0 -0
  77. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/test_assert.py +0 -0
  78. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/test_custom_keyword.py +0 -0
  79. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/test_http.py +0 -0
  80. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/examples/test_quickstart.py +0 -0
  81. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/keywords/__init__.py +0 -0
  82. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/keywords/assertion_keywords.py +0 -0
  83. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/keywords/global_keywords.py +0 -0
  84. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/keywords/http_keywords.py +0 -0
  85. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/keywords/system_keywords.py +0 -0
  86. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/main_adapter.py +0 -0
  87. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/remote/__init__.py +0 -0
  88. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/remote/hook_manager.py +0 -0
  89. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/remote/keyword_client.py +0 -0
  90. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/remote/keyword_server.py +0 -0
  91. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/remote/variable_bridge.py +0 -0
  92. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl/templates/keywords_report.html +0 -0
  93. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl.egg-info/dependency_links.txt +0 -0
  94. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl.egg-info/entry_points.txt +0 -0
  95. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl.egg-info/requires.txt +0 -0
  96. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/pytest_dsl.egg-info/top_level.txt +0 -0
  97. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/setup.cfg +0 -0
  98. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/setup.py +0 -0
  99. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/mock_config.yaml +0 -0
  100. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_end_to_end_seamless.py +0 -0
  101. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_enhanced_variable_access.py +0 -0
  102. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_http_assertions_extractors.py +0 -0
  103. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_mock_server.py +0 -0
  104. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_seamless_variable_sync.py +0 -0
  105. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_variable_sync.py +0 -0
  106. {pytest_dsl-0.13.0 → pytest_dsl-0.15.0}/tests/test_variable_sync_demo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-dsl
3
- Version: 0.13.0
3
+ Version: 0.15.0
4
4
  Summary: A DSL testing framework based on pytest
5
5
  Author: Chen Shuanglin
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytest-dsl"
7
- version = "0.13.0"
7
+ version = "0.15.0"
8
8
  description = "A DSL testing framework based on pytest"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,197 @@
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
+ Executor = DSLExecutor
59
+ Validator = DSLValidator
60
+ HookManager = DSLHookManager
61
+ KeywordLoader = KeywordLoader
62
+
63
+ # 导出所有公共接口
64
+ __all__ = [
65
+ # 版本信息
66
+ '__version__',
67
+
68
+ # 核心执行器
69
+ 'DSLExecutor', 'Executor',
70
+
71
+ # 关键字管理
72
+ 'keyword_manager', 'KeywordManager',
73
+ 'custom_keyword_manager',
74
+
75
+ # 关键字加载器
76
+ 'keyword_loader', 'KeywordLoader',
77
+ 'load_all_keywords', 'categorize_keyword', 'get_keyword_source_info',
78
+ 'group_keywords_by_source', 'scan_project_custom_keywords',
79
+
80
+ # Hook系统
81
+ 'hookimpl', 'hookspec', 'DSLHookSpecs',
82
+ 'hook_manager', 'DSLHookManager', 'HookManager',
83
+
84
+ # DSL校验
85
+ 'DSLValidator', 'Validator',
86
+ 'DSLValidationError',
87
+ 'validate_dsl',
88
+ 'check_dsl_syntax',
89
+
90
+ # 自动装饰器
91
+ 'auto_dsl',
92
+
93
+ # 核心组件
94
+ 'Node', 'get_parser', 'get_lexer',
95
+ 'TestContext', 'global_context',
96
+ 'VariableReplacer',
97
+ ]
98
+
99
+ # 快捷函数
100
+
101
+
102
+ def create_executor(enable_hooks: bool = True) -> DSLExecutor:
103
+ """创建DSL执行器实例
104
+
105
+ Args:
106
+ enable_hooks: 是否启用hook机制
107
+
108
+ Returns:
109
+ DSL执行器实例
110
+ """
111
+ return DSLExecutor(enable_hooks=enable_hooks)
112
+
113
+
114
+ def parse_dsl(content: str) -> Node:
115
+ """解析DSL内容为AST
116
+
117
+ Args:
118
+ content: DSL内容
119
+
120
+ Returns:
121
+ 解析后的AST根节点
122
+ """
123
+ lexer = get_lexer()
124
+ parser = get_parser()
125
+ return parser.parse(content, lexer=lexer)
126
+
127
+
128
+ def execute_dsl(content: str, context: dict = None, enable_hooks: bool = True) -> any:
129
+ """执行DSL内容的便捷函数
130
+
131
+ Args:
132
+ content: DSL内容
133
+ context: 执行上下文(可选)
134
+ enable_hooks: 是否启用hook机制
135
+
136
+ Returns:
137
+ 执行结果
138
+ """
139
+ executor = create_executor(enable_hooks=enable_hooks)
140
+ if context:
141
+ executor.variables.update(context)
142
+ for key, value in context.items():
143
+ executor.test_context.set(key, value)
144
+
145
+ ast = parse_dsl(content)
146
+ return executor.execute(ast)
147
+
148
+
149
+ def register_keyword(name: str, parameters: list = None, source_type: str = "external",
150
+ source_name: str = "user_defined"):
151
+ """注册关键字的装饰器
152
+
153
+ Args:
154
+ name: 关键字名称
155
+ parameters: 参数列表
156
+ source_type: 来源类型
157
+ source_name: 来源名称
158
+
159
+ Returns:
160
+ 装饰器函数
161
+ """
162
+ if parameters is None:
163
+ parameters = []
164
+
165
+ return keyword_manager.register_with_source(
166
+ name=name,
167
+ parameters=parameters,
168
+ source_type=source_type,
169
+ source_name=source_name
170
+ )
171
+
172
+
173
+ # 版本兼容性检查
174
+ def check_version_compatibility():
175
+ """检查版本兼容性"""
176
+ try:
177
+ import sys
178
+ if sys.version_info < (3, 7):
179
+ import warnings
180
+ warnings.warn(
181
+ "pytest-dsl 需要 Python 3.7 或更高版本",
182
+ UserWarning,
183
+ stacklevel=2
184
+ )
185
+ except Exception:
186
+ pass
187
+
188
+
189
+ # 初始化时进行版本检查
190
+ check_version_compatibility()
191
+
192
+ # 自动初始化hook管理器
193
+ try:
194
+ hook_manager.initialize()
195
+ except Exception as e:
196
+ import warnings
197
+ warnings.warn(f"Hook管理器初始化失败: {e}", UserWarning, stacklevel=2)
@@ -16,8 +16,9 @@ from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
16
16
  from pytest_dsl.core.auto_directory import (
17
17
  SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
18
18
  )
19
- from pytest_dsl.core.plugin_discovery import (
20
- load_all_plugins, scan_local_keywords
19
+ from pytest_dsl.core.keyword_loader import (
20
+ load_all_keywords, categorize_keyword, get_keyword_source_info,
21
+ group_keywords_by_source, scan_project_custom_keywords
21
22
  )
22
23
  from pytest_dsl.core.keyword_manager import keyword_manager
23
24
 
@@ -138,164 +139,7 @@ def parse_args():
138
139
  return args
139
140
 
140
141
 
141
- def load_all_keywords(include_remote=False):
142
- """加载所有可用的关键字
143
142
 
144
- Args:
145
- include_remote: 是否包含远程关键字,默认为False
146
- """
147
- # 首先导入内置关键字模块,确保内置关键字被注册
148
- try:
149
- import pytest_dsl.keywords # noqa: F401
150
- print("内置关键字模块加载完成")
151
- except ImportError as e:
152
- print(f"加载内置关键字模块失败: {e}")
153
-
154
- # 加载已安装的关键字插件
155
- load_all_plugins()
156
-
157
- # 扫描本地关键字
158
- scan_local_keywords()
159
-
160
- # 自动导入项目中的resources目录
161
- try:
162
- from pytest_dsl.core.custom_keyword_manager import (
163
- custom_keyword_manager)
164
-
165
- # 尝试从多个可能的项目根目录位置导入resources
166
- possible_roots = [
167
- os.getcwd(), # 当前工作目录
168
- os.path.dirname(os.getcwd()), # 上级目录
169
- ]
170
-
171
- # 尝试每个可能的根目录
172
- for project_root in possible_roots:
173
- if project_root and os.path.exists(project_root):
174
- resources_dir = os.path.join(project_root, "resources")
175
- if (os.path.exists(resources_dir) and
176
- os.path.isdir(resources_dir)):
177
- custom_keyword_manager.auto_import_resources_directory(
178
- project_root)
179
- break
180
- except Exception as e:
181
- print(f"自动导入resources目录时出现警告: {str(e)}")
182
-
183
- # 扫描项目中的自定义关键字(.resource文件中定义的)
184
- project_custom_keywords = scan_project_custom_keywords()
185
- if project_custom_keywords:
186
- print(f"发现 {len(project_custom_keywords)} 个项目自定义关键字")
187
-
188
- # 加载.resource文件中的关键字到关键字管理器
189
- from pytest_dsl.core.custom_keyword_manager import (
190
- custom_keyword_manager
191
- )
192
- from pathlib import Path
193
-
194
- project_root = Path(os.getcwd())
195
- resource_files = list(project_root.glob('**/*.resource'))
196
-
197
- for resource_file in resource_files:
198
- try:
199
- custom_keyword_manager.load_resource_file(str(resource_file))
200
- print(f"已加载资源文件: {resource_file}")
201
- except Exception as e:
202
- print(f"加载资源文件失败 {resource_file}: {e}")
203
-
204
- # 根据参数决定是否加载远程关键字
205
- if include_remote:
206
- print("正在扫描远程关键字...")
207
- # 这里可以添加远程关键字的扫描逻辑
208
- # 目前远程关键字是通过DSL文件中的@remote导入指令动态加载的
209
- else:
210
- print("跳过远程关键字扫描")
211
-
212
- return project_custom_keywords
213
-
214
-
215
- def categorize_keyword(keyword_name, keyword_info,
216
- project_custom_keywords=None):
217
- """判断关键字的类别"""
218
- # 优先使用存储的来源信息
219
- source_type = keyword_info.get('source_type')
220
- if source_type:
221
- if source_type == 'builtin':
222
- return 'builtin'
223
- elif source_type == 'plugin':
224
- return 'plugin'
225
- elif source_type in ['external', 'local']:
226
- return 'custom'
227
- elif source_type == 'project_custom':
228
- return 'project_custom'
229
-
230
- # 向后兼容:使用原有的判断逻辑
231
- if keyword_info.get('remote', False):
232
- return 'remote'
233
-
234
- # 检查是否是项目自定义关键字(DSL文件中定义的)
235
- if project_custom_keywords and keyword_name in project_custom_keywords:
236
- return 'project_custom'
237
-
238
- # 检查是否是内置关键字(通过检查函数所在模块)
239
- func = keyword_info.get('func')
240
- if func and hasattr(func, '__module__'):
241
- module_name = func.__module__
242
- if module_name and module_name.startswith('pytest_dsl.keywords'):
243
- return 'builtin'
244
-
245
- return 'custom'
246
-
247
-
248
- def get_keyword_source_info(keyword_info):
249
- """获取关键字的详细来源信息"""
250
- source_type = keyword_info.get('source_type', 'unknown')
251
- source_name = keyword_info.get('source_name', '未知')
252
-
253
- return {
254
- 'type': source_type,
255
- 'name': source_name,
256
- 'display_name': source_name,
257
- 'module': keyword_info.get('module_name', ''),
258
- 'plugin_module': keyword_info.get('plugin_module', '')
259
- }
260
-
261
-
262
- def group_keywords_by_source(keywords_dict, project_custom_keywords=None):
263
- """按来源分组关键字
264
-
265
- Returns:
266
- dict: 格式为 {source_group: {source_name: [keywords]}}
267
- """
268
- groups = {
269
- 'builtin': {},
270
- 'plugin': {},
271
- 'custom': {},
272
- 'project_custom': {},
273
- 'remote': {}
274
- }
275
-
276
- for keyword_name, keyword_info in keywords_dict.items():
277
- category = categorize_keyword(
278
- keyword_name, keyword_info, project_custom_keywords
279
- )
280
- source_info = get_keyword_source_info(keyword_info)
281
-
282
- # 特殊处理项目自定义关键字
283
- if category == 'project_custom' and project_custom_keywords:
284
- custom_info = project_custom_keywords[keyword_name]
285
- source_name = custom_info['file']
286
- else:
287
- source_name = source_info['name']
288
-
289
- if source_name not in groups[category]:
290
- groups[category][source_name] = []
291
-
292
- groups[category][source_name].append({
293
- 'name': keyword_name,
294
- 'info': keyword_info,
295
- 'source_info': source_info
296
- })
297
-
298
- return groups
299
143
 
300
144
 
301
145
  def format_keyword_info_text(keyword_name, keyword_info, show_category=True,
@@ -724,12 +568,17 @@ def list_keywords(output_format='json', name_filter=None,
724
568
 
725
569
  def load_yaml_variables(args):
726
570
  """从命令行参数加载YAML变量"""
727
- # 使用统一的加载函数,包含远程服务器自动连接功能
571
+ # 使用统一的加载函数,包含远程服务器自动连接功能和hook支持
728
572
  try:
573
+ # 尝试从环境变量获取环境名称
574
+ environment = (os.environ.get('PYTEST_DSL_ENVIRONMENT') or
575
+ os.environ.get('ENVIRONMENT'))
576
+
729
577
  load_yaml_variables_from_args(
730
578
  yaml_files=args.yaml_vars,
731
579
  yaml_vars_dir=args.yaml_vars_dir,
732
- project_root=os.getcwd() # CLI模式下使用当前工作目录作为项目根目录
580
+ project_root=os.getcwd(), # CLI模式下使用当前工作目录作为项目根目录
581
+ environment=environment
733
582
  )
734
583
  except Exception as e:
735
584
  print(f"加载YAML变量失败: {str(e)}")
@@ -774,6 +623,33 @@ def run_dsl_tests(args):
774
623
  # 加载YAML变量(包括远程服务器自动连接)
775
624
  load_yaml_variables(args)
776
625
 
626
+ # 支持hook机制的执行
627
+ from pytest_dsl.core.hookable_executor import hookable_executor
628
+
629
+ # 检查是否有hook提供的用例列表
630
+ hook_cases = hookable_executor.list_dsl_cases()
631
+ if hook_cases:
632
+ # 如果有hook提供的用例,优先执行这些用例
633
+ print(f"通过Hook发现 {len(hook_cases)} 个DSL用例")
634
+ failures = 0
635
+ for case in hook_cases:
636
+ case_id = case.get('id') or case.get('name', 'unknown')
637
+ try:
638
+ print(f"执行用例: {case.get('name', case_id)}")
639
+ hookable_executor.execute_dsl(str(case_id))
640
+ print(f"✓ 用例 {case.get('name', case_id)} 执行成功")
641
+ except Exception as e:
642
+ print(f"✗ 用例 {case.get('name', case_id)} 执行失败: {e}")
643
+ failures += 1
644
+
645
+ if failures > 0:
646
+ print(f"总计 {failures}/{len(hook_cases)} 个测试失败")
647
+ sys.exit(1)
648
+ else:
649
+ print(f"所有 {len(hook_cases)} 个测试成功完成")
650
+ return
651
+
652
+ # 如果没有hook用例,使用传统的文件执行方式
777
653
  lexer = get_lexer()
778
654
  parser = get_parser()
779
655
  executor = DSLExecutor()
@@ -900,106 +776,7 @@ def main_list_keywords():
900
776
  )
901
777
 
902
778
 
903
- def scan_project_custom_keywords(project_root=None):
904
- """扫描项目中.resource文件中的自定义关键字
905
-
906
- Args:
907
- project_root: 项目根目录,默认为当前工作目录
908
-
909
- Returns:
910
- dict: 自定义关键字信息,格式为
911
- {keyword_name: {'file': file_path, 'node': ast_node}}
912
- """
913
- if project_root is None:
914
- project_root = os.getcwd()
915
-
916
- project_root = Path(project_root)
917
- custom_keywords = {}
918
-
919
- # 查找所有.resource文件
920
- resource_files = list(project_root.glob('**/*.resource'))
921
-
922
- if not resource_files:
923
- return custom_keywords
924
-
925
- lexer = get_lexer()
926
- parser = get_parser()
927
-
928
- for file_path in resource_files:
929
- try:
930
- # 读取并解析文件
931
- content = read_file(str(file_path))
932
- ast = parser.parse(content, lexer=lexer)
933
-
934
- # 查找自定义关键字定义
935
- keywords_in_file = extract_custom_keywords_from_ast(
936
- ast, str(file_path)
937
- )
938
- custom_keywords.update(keywords_in_file)
939
-
940
- except Exception as e:
941
- print(f"解析资源文件 {file_path} 时出错: {e}")
942
-
943
- return custom_keywords
944
-
945
-
946
- def extract_custom_keywords_from_ast(ast, file_path):
947
- """从AST中提取自定义关键字定义
948
-
949
- Args:
950
- ast: 抽象语法树
951
- file_path: 文件路径
952
-
953
- Returns:
954
- dict: 自定义关键字信息
955
- """
956
- custom_keywords = {}
957
-
958
- if ast.type != 'Start' or len(ast.children) < 2:
959
- return custom_keywords
960
-
961
- # 遍历语句节点
962
- statements_node = ast.children[1]
963
- if statements_node.type != 'Statements':
964
- return custom_keywords
965
-
966
- for node in statements_node.children:
967
- # 支持两种格式:CustomKeyword(旧格式)和Function(新格式)
968
- if node.type in ['CustomKeyword', 'Function']:
969
- keyword_name = node.value
970
-
971
- # 提取参数信息
972
- params_node = node.children[0] if node.children else None
973
- parameters = []
974
-
975
- if params_node:
976
- for param in params_node:
977
- param_name = param.value
978
- param_default = None
979
-
980
- # 检查是否有默认值
981
- if param.children and param.children[0]:
982
- param_default = param.children[0].value
983
-
984
- param_info = {
985
- 'name': param_name,
986
- 'mapping': param_name,
987
- 'description': f'自定义关键字参数 {param_name}'
988
- }
989
-
990
- if param_default is not None:
991
- param_info['default'] = param_default
992
-
993
- parameters.append(param_info)
994
-
995
- custom_keywords[keyword_name] = {
996
- 'file': file_path,
997
- 'node': node,
998
- 'type': 'project_custom',
999
- 'parameters': parameters
1000
- }
1001
779
 
1002
- return custom_keywords
1003
780
 
1004
781
 
1005
782
  if __name__ == '__main__':