|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +测试VertexGroup exposed_variables修复功能的专项测试 |
| 4 | +本测试文件专门验证修复后的VertexGroup exposed_variables功能 |
| 5 | +""" |
| 6 | + |
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
| 10 | +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) |
| 11 | + |
| 12 | +from vertex_flow.workflow.constants import LOCAL_VAR, SOURCE_SCOPE, SOURCE_VAR, SUBGRAPH_SOURCE |
| 13 | +from vertex_flow.workflow.vertex.function_vertex import FunctionVertex |
| 14 | +from vertex_flow.workflow.vertex.vertex_group import VertexGroup |
| 15 | + |
| 16 | + |
| 17 | +def test_exposed_variables_flattened_output(): |
| 18 | + """测试exposed_variables配置时返回扁平化输出""" |
| 19 | + print("=== 测试exposed_variables扁平化输出 ===") |
| 20 | + |
| 21 | + def task_a(inputs): |
| 22 | + value = inputs.get("input_value", 0) |
| 23 | + return {"result_a": value * 2, "internal_data": "secret"} |
| 24 | + |
| 25 | + def task_b(inputs): |
| 26 | + result_a = inputs.get("result_a", 0) |
| 27 | + return {"result_b": result_a + 10, "debug_info": "processed"} |
| 28 | + |
| 29 | + vertex_a = FunctionVertex( |
| 30 | + id="vertex_a", |
| 31 | + name="Vertex A", |
| 32 | + task=task_a, |
| 33 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 34 | + ) |
| 35 | + |
| 36 | + vertex_b = FunctionVertex( |
| 37 | + id="vertex_b", |
| 38 | + name="Vertex B", |
| 39 | + task=task_b, |
| 40 | + variables=[{SOURCE_SCOPE: "vertex_a", SOURCE_VAR: "result_a", LOCAL_VAR: "result_a"}], |
| 41 | + ) |
| 42 | + |
| 43 | + vertex_group = VertexGroup( |
| 44 | + id="test_group", |
| 45 | + name="Test Group", |
| 46 | + subgraph_vertices=[vertex_a, vertex_b], |
| 47 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 48 | + exposed_variables=[ |
| 49 | + {SOURCE_SCOPE: "vertex_a", SOURCE_VAR: "result_a", LOCAL_VAR: "exposed_a"}, |
| 50 | + {SOURCE_SCOPE: "vertex_b", SOURCE_VAR: "result_b", LOCAL_VAR: "exposed_b"}, |
| 51 | + ], |
| 52 | + ) |
| 53 | + |
| 54 | + result = vertex_group.execute(inputs={"input_value": 5}) |
| 55 | + print(f"VertexGroup执行结果: {result}") |
| 56 | + |
| 57 | + # 验证扁平化输出 |
| 58 | + assert "exposed_a" in result, "应该包含暴露的变量exposed_a" |
| 59 | + assert "exposed_b" in result, "应该包含暴露的变量exposed_b" |
| 60 | + assert result["exposed_a"] == 10, f"期望exposed_a为10,实际为{result['exposed_a']}" |
| 61 | + assert result["exposed_b"] == 20, f"期望exposed_b为20,实际为{result['exposed_b']}" |
| 62 | + |
| 63 | + # 验证不包含未暴露的变量 |
| 64 | + assert "vertex_a" not in result, "不应该包含子图顶点的嵌套输出" |
| 65 | + assert "vertex_b" not in result, "不应该包含子图顶点的嵌套输出" |
| 66 | + assert "internal_data" not in result, "不应该包含未暴露的内部变量" |
| 67 | + assert "debug_info" not in result, "不应该包含未暴露的调试信息" |
| 68 | + |
| 69 | + print("✓ exposed_variables扁平化输出测试通过") |
| 70 | + |
| 71 | + |
| 72 | +def test_no_exposed_variables_backward_compatibility(): |
| 73 | + """测试没有exposed_variables时的向后兼容性""" |
| 74 | + print("\n=== 测试向后兼容性(无exposed_variables) ===") |
| 75 | + |
| 76 | + def task_simple(inputs): |
| 77 | + value = inputs.get("input_value", 0) |
| 78 | + return {"result": value * 3, "status": "completed"} |
| 79 | + |
| 80 | + vertex_simple = FunctionVertex( |
| 81 | + id="vertex_simple", |
| 82 | + name="Simple Vertex", |
| 83 | + task=task_simple, |
| 84 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 85 | + ) |
| 86 | + |
| 87 | + # 不配置exposed_variables |
| 88 | + vertex_group = VertexGroup( |
| 89 | + id="backward_group", |
| 90 | + name="Backward Compatibility Group", |
| 91 | + subgraph_vertices=[vertex_simple], |
| 92 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 93 | + # 故意不设置exposed_variables |
| 94 | + ) |
| 95 | + |
| 96 | + result = vertex_group.execute(inputs={"input_value": 4}) |
| 97 | + print(f"VertexGroup执行结果: {result}") |
| 98 | + |
| 99 | + # 验证向后兼容性:应该返回子图顶点的输出 |
| 100 | + assert "vertex_simple" in result, "向后兼容:应该包含子图顶点的输出" |
| 101 | + assert result["vertex_simple"]["result"] == 12, f"期望结果为12,实际为{result['vertex_simple']['result']}" |
| 102 | + assert result["vertex_simple"]["status"] == "completed", "应该包含所有子图顶点的输出" |
| 103 | + |
| 104 | + print("✓ 向后兼容性测试通过") |
| 105 | + |
| 106 | + |
| 107 | +def test_exposed_variables_edge_cases(): |
| 108 | + """测试exposed_variables的边界情况""" |
| 109 | + print("\n=== 测试exposed_variables边界情况 ===") |
| 110 | + |
| 111 | + def task_with_none(inputs): |
| 112 | + value = inputs.get("input_value", 0) |
| 113 | + return {"result": value if value > 0 else None, "always_present": "exists"} |
| 114 | + |
| 115 | + def task_with_empty(inputs): |
| 116 | + return {"empty_dict": {}, "empty_list": [], "zero_value": 0} |
| 117 | + |
| 118 | + vertex_none = FunctionVertex( |
| 119 | + id="vertex_none", |
| 120 | + name="Vertex with None", |
| 121 | + task=task_with_none, |
| 122 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 123 | + ) |
| 124 | + |
| 125 | + vertex_empty = FunctionVertex( |
| 126 | + id="vertex_empty", |
| 127 | + name="Vertex with Empty Values", |
| 128 | + task=task_with_empty, |
| 129 | + ) |
| 130 | + |
| 131 | + vertex_group = VertexGroup( |
| 132 | + id="edge_case_group", |
| 133 | + name="Edge Case Group", |
| 134 | + subgraph_vertices=[vertex_none, vertex_empty], |
| 135 | + variables=[{SOURCE_SCOPE: SUBGRAPH_SOURCE, SOURCE_VAR: "input_value", LOCAL_VAR: "input_value"}], |
| 136 | + exposed_variables=[ |
| 137 | + {SOURCE_SCOPE: "vertex_none", SOURCE_VAR: "result", LOCAL_VAR: "nullable_result"}, |
| 138 | + {SOURCE_SCOPE: "vertex_none", SOURCE_VAR: "always_present", LOCAL_VAR: "always_there"}, |
| 139 | + {SOURCE_SCOPE: "vertex_empty", SOURCE_VAR: "empty_dict", LOCAL_VAR: "empty_dict_exposed"}, |
| 140 | + {SOURCE_SCOPE: "vertex_empty", SOURCE_VAR: "zero_value", LOCAL_VAR: "zero_exposed"}, |
| 141 | + ], |
| 142 | + ) |
| 143 | + |
| 144 | + # 测试正值情况 |
| 145 | + result_positive = vertex_group.execute(inputs={"input_value": 5}) |
| 146 | + print(f"正值输入结果: {result_positive}") |
| 147 | + |
| 148 | + # 验证存在的变量 |
| 149 | + if "nullable_result" in result_positive: |
| 150 | + assert result_positive["nullable_result"] == 5, "正值应该被正确暴露" |
| 151 | + if "always_there" in result_positive: |
| 152 | + assert result_positive["always_there"] == "exists", "非空值应该被正确暴露" |
| 153 | + if "empty_dict_exposed" in result_positive: |
| 154 | + assert result_positive["empty_dict_exposed"] == {}, "空字典应该被正确暴露" |
| 155 | + if "zero_exposed" in result_positive: |
| 156 | + assert result_positive["zero_exposed"] == 0, "零值应该被正确暴露" |
| 157 | + |
| 158 | + # 测试零值情况 |
| 159 | + result_zero = vertex_group.execute(inputs={"input_value": 0}) |
| 160 | + print(f"零值输入结果: {result_zero}") |
| 161 | + |
| 162 | + # 验证存在的变量 |
| 163 | + if "nullable_result" in result_zero: |
| 164 | + assert result_zero["nullable_result"] is None, "None值应该被正确暴露" |
| 165 | + if "always_there" in result_zero: |
| 166 | + assert result_zero["always_there"] == "exists", "非空值应该被正确暴露" |
| 167 | + |
| 168 | + print("✓ 边界情况测试通过") |
| 169 | + |
| 170 | + |
| 171 | +def test_exposed_variables_name_conflicts(): |
| 172 | + """测试exposed_variables名称冲突处理""" |
| 173 | + print("\n=== 测试exposed_variables名称冲突 ===") |
| 174 | + |
| 175 | + def task_conflict_a(inputs): |
| 176 | + return {"same_name": "from_a", "unique_a": "value_a"} |
| 177 | + |
| 178 | + def task_conflict_b(inputs): |
| 179 | + return {"same_name": "from_b", "unique_b": "value_b"} |
| 180 | + |
| 181 | + vertex_a = FunctionVertex(id="conflict_a", name="Conflict A", task=task_conflict_a) |
| 182 | + vertex_b = FunctionVertex(id="conflict_b", name="Conflict B", task=task_conflict_b) |
| 183 | + |
| 184 | + vertex_group = VertexGroup( |
| 185 | + id="conflict_group", |
| 186 | + name="Conflict Group", |
| 187 | + subgraph_vertices=[vertex_a, vertex_b], |
| 188 | + exposed_variables=[ |
| 189 | + {SOURCE_SCOPE: "conflict_a", SOURCE_VAR: "same_name", LOCAL_VAR: "exposed_a"}, |
| 190 | + {SOURCE_SCOPE: "conflict_b", SOURCE_VAR: "same_name", LOCAL_VAR: "exposed_b"}, |
| 191 | + {SOURCE_SCOPE: "conflict_a", SOURCE_VAR: "unique_a", LOCAL_VAR: "unique_from_a"}, |
| 192 | + {SOURCE_SCOPE: "conflict_b", SOURCE_VAR: "unique_b", LOCAL_VAR: "unique_from_b"}, |
| 193 | + ], |
| 194 | + ) |
| 195 | + |
| 196 | + result = vertex_group.execute(inputs={}) |
| 197 | + print(f"名称冲突处理结果: {result}") |
| 198 | + |
| 199 | + # 验证不同的暴露名称能正确区分来源 |
| 200 | + assert result["exposed_a"] == "from_a", "来自vertex_a的值应该正确暴露" |
| 201 | + assert result["exposed_b"] == "from_b", "来自vertex_b的值应该正确暴露" |
| 202 | + assert result["unique_from_a"] == "value_a", "unique_a应该正确暴露" |
| 203 | + assert result["unique_from_b"] == "value_b", "unique_b应该正确暴露" |
| 204 | + |
| 205 | + print("✓ 名称冲突处理测试通过") |
| 206 | + |
| 207 | + |
| 208 | +def test_exposed_variables_complex_data_types(): |
| 209 | + """测试exposed_variables处理复杂数据类型""" |
| 210 | + print("\n=== 测试复杂数据类型暴露 ===") |
| 211 | + |
| 212 | + def task_complex(inputs): |
| 213 | + return { |
| 214 | + "nested_dict": {"level1": {"level2": "deep_value"}}, |
| 215 | + "list_data": [1, 2, {"nested": "in_list"}], |
| 216 | + "mixed_types": {"string": "text", "number": 42, "boolean": True, "null": None, "list": ["a", "b", "c"]}, |
| 217 | + } |
| 218 | + |
| 219 | + vertex_complex = FunctionVertex(id="complex_vertex", name="Complex Data Vertex", task=task_complex) |
| 220 | + |
| 221 | + vertex_group = VertexGroup( |
| 222 | + id="complex_group", |
| 223 | + name="Complex Data Group", |
| 224 | + subgraph_vertices=[vertex_complex], |
| 225 | + exposed_variables=[ |
| 226 | + {SOURCE_SCOPE: "complex_vertex", SOURCE_VAR: "nested_dict", LOCAL_VAR: "exposed_nested"}, |
| 227 | + {SOURCE_SCOPE: "complex_vertex", SOURCE_VAR: "list_data", LOCAL_VAR: "exposed_list"}, |
| 228 | + {SOURCE_SCOPE: "complex_vertex", SOURCE_VAR: "mixed_types", LOCAL_VAR: "exposed_mixed"}, |
| 229 | + ], |
| 230 | + ) |
| 231 | + |
| 232 | + result = vertex_group.execute(inputs={}) |
| 233 | + print(f"复杂数据类型结果: {result}") |
| 234 | + |
| 235 | + # 验证复杂数据类型的正确暴露 |
| 236 | + assert result["exposed_nested"]["level1"]["level2"] == "deep_value", "嵌套字典应该正确暴露" |
| 237 | + assert result["exposed_list"][2]["nested"] == "in_list", "列表中的嵌套数据应该正确暴露" |
| 238 | + assert result["exposed_mixed"]["string"] == "text", "混合类型中的字符串应该正确暴露" |
| 239 | + assert result["exposed_mixed"]["number"] == 42, "混合类型中的数字应该正确暴露" |
| 240 | + assert result["exposed_mixed"]["boolean"] is True, "混合类型中的布尔值应该正确暴露" |
| 241 | + assert result["exposed_mixed"]["null"] is None, "混合类型中的None值应该正确暴露" |
| 242 | + assert result["exposed_mixed"]["list"] == ["a", "b", "c"], "混合类型中的列表应该正确暴露" |
| 243 | + |
| 244 | + print("✓ 复杂数据类型测试通过") |
| 245 | + |
| 246 | + |
| 247 | +def test_exposed_variables_missing_source(): |
| 248 | + """测试exposed_variables引用不存在的源变量""" |
| 249 | + print("\n=== 测试引用不存在的源变量 ===") |
| 250 | + |
| 251 | + def task_normal(inputs): |
| 252 | + return {"existing_var": "exists"} |
| 253 | + |
| 254 | + vertex_normal = FunctionVertex(id="normal_vertex", name="Normal Vertex", task=task_normal) |
| 255 | + |
| 256 | + vertex_group = VertexGroup( |
| 257 | + id="missing_source_group", |
| 258 | + name="Missing Source Group", |
| 259 | + subgraph_vertices=[vertex_normal], |
| 260 | + exposed_variables=[ |
| 261 | + {SOURCE_SCOPE: "normal_vertex", SOURCE_VAR: "existing_var", LOCAL_VAR: "exposed_existing"}, |
| 262 | + {SOURCE_SCOPE: "normal_vertex", SOURCE_VAR: "non_existing_var", LOCAL_VAR: "exposed_missing"}, |
| 263 | + {SOURCE_SCOPE: "non_existing_vertex", SOURCE_VAR: "any_var", LOCAL_VAR: "exposed_from_missing_vertex"}, |
| 264 | + ], |
| 265 | + ) |
| 266 | + |
| 267 | + result = vertex_group.execute(inputs={}) |
| 268 | + print(f"缺失源变量处理结果: {result}") |
| 269 | + |
| 270 | + # 验证存在的变量正常暴露 |
| 271 | + assert "exposed_existing" in result, "存在的变量应该被正确暴露" |
| 272 | + assert result["exposed_existing"] == "exists", "存在的变量值应该正确" |
| 273 | + |
| 274 | + # 验证不存在的变量不会导致错误,但也不会出现在结果中 |
| 275 | + # 根据实际实现,不存在的变量会被忽略 |
| 276 | + assert "exposed_missing" not in result, "不存在的变量不应该出现在结果中" |
| 277 | + assert "exposed_from_missing_vertex" not in result, "来自不存在顶点的变量不应该出现在结果中" |
| 278 | + print(f"结果中的键: {list(result.keys())}") |
| 279 | + |
| 280 | + print("✓ 缺失源变量处理测试通过") |
| 281 | + |
| 282 | + |
| 283 | +def main(): |
| 284 | + """主测试函数""" |
| 285 | + try: |
| 286 | + test_exposed_variables_flattened_output() |
| 287 | + test_no_exposed_variables_backward_compatibility() |
| 288 | + test_exposed_variables_edge_cases() |
| 289 | + test_exposed_variables_name_conflicts() |
| 290 | + test_exposed_variables_complex_data_types() |
| 291 | + test_exposed_variables_missing_source() |
| 292 | + print("\n🎉 所有VertexGroup exposed_variables修复测试通过!") |
| 293 | + except Exception as e: |
| 294 | + print(f"\n❌ 测试失败: {e}") |
| 295 | + import traceback |
| 296 | + |
| 297 | + traceback.print_exc() |
| 298 | + |
| 299 | + |
| 300 | +if __name__ == "__main__": |
| 301 | + main() |
0 commit comments