Skip to content

Commit 1e70fb1

Browse files
CodeCasterXclaude
andcommitted
test: 添加 NPE 复现测试用于 GitHub Actions 验证
问题背景: - PatternTest.shouldOkWhenAiFlowWithExampleSelector 在 GitHub Actions 中偶发失败 - 错误: NullPointerException at Tip.merge(Tip.java:121) - 失败率: 0.5% (5次/1000次运行, 平均每22天一次) - 只在 GitHub Actions 环境中出现,本地无法稳定复现 测试策略(TDD 红色阶段): 1. shouldReproduceNPEInRunnableParallel (50次重复) - 使用 200ms 延迟制造快慢分支,增大竞态窗口 - 预期:在 GitHub Actions 中应该能偶发触发 NPE 2. shouldReproduceOriginalTestFailure (20次重复) - 使用原始测试配置重复运行 - 预期:在 GitHub Actions 中应该能偶发触发 NPE 验证目标: - 如果这些测试在 GitHub Actions 中失败(NPE),证明测试有效 - 如果全部通过,需要调整延迟时间或重复次数 - 为后续修复提供可靠的验证基准 下一步: - 推送到 99.99.x 分支触发 GitHub Actions - 观察测试结果,确认能够复现 NPE - 然后添加修复代码,再次验证 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 9d405de commit 1e70fb1

File tree

1 file changed

+57
-0
lines changed
  • framework/fel/java/fel-flow/src/test/java/modelengine/fel/engine/operators

1 file changed

+57
-0
lines changed

framework/fel/java/fel-flow/src/test/java/modelengine/fel/engine/operators/PatternTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import modelengine.fitframework.util.StringUtils;
4343

4444
import org.junit.jupiter.api.DisplayName;
45+
import org.junit.jupiter.api.RepeatedTest;
4546
import org.junit.jupiter.api.Test;
4647

4748
import java.util.Collection;
@@ -97,6 +98,62 @@ void shouldOkWhenAiFlowWithExampleSelector() {
9798
assertThat(converse.offer("1+2").await().text()).isEqualTo("2+2=4\n2+3=5\n1+2=");
9899
}
99100

101+
@RepeatedTest(50)
102+
@DisplayName("【复现测试】强制触发 runnableParallel 的竞态条件 NPE")
103+
void shouldReproduceNPEInRunnableParallel() {
104+
// 慢分支:模拟 fewShot() 的延迟,增大竞态窗口
105+
modelengine.fel.engine.operators.patterns.SyncTipper<String> slowBranch = arg -> {
106+
try {
107+
Thread.sleep(200); // 200ms 延迟
108+
} catch (InterruptedException e) {
109+
Thread.currentThread().interrupt();
110+
}
111+
return Tip.from("slow", "slow-value");
112+
};
113+
114+
// 快分支:模拟 question() 的快速返回
115+
modelengine.fel.engine.operators.patterns.SyncTipper<String> fastBranch = arg -> {
116+
return Tip.from("fast", "fast-value");
117+
};
118+
119+
// 执行并发聚合
120+
Conversation<String, Prompt> converse = AiFlows.<String>create()
121+
.runnableParallel(fastBranch, slowBranch)
122+
.prompt(Prompts.human("{{fast}} {{slow}}"))
123+
.close()
124+
.converse();
125+
126+
// 在修复前:这里应该偶发抛出 NullPointerException
127+
// 在修复后:这里应该稳定通过
128+
String result = converse.offer("test").await().text();
129+
130+
assertThat(result).contains("fast-value", "slow-value");
131+
}
132+
133+
@RepeatedTest(20)
134+
@DisplayName("【复现测试】使用原测试配置重复运行")
135+
void shouldReproduceOriginalTestFailure() {
136+
// 使用原始失败测试的完全相同配置
137+
Example[] examples = {new DefaultExample("2+2", "4"), new DefaultExample("2+3", "5")};
138+
139+
Conversation<String, Prompt> converse = AiFlows.<String>create()
140+
.runnableParallel(
141+
question(),
142+
fewShot(ExampleSelector.builder()
143+
.template("{{q}}={{a}}", "q", "a")
144+
.delimiter("\n")
145+
.example(examples)
146+
.build()))
147+
.prompt(Prompts.human("{{examples}}\n{{question}}="))
148+
.close()
149+
.converse();
150+
151+
// 在修复前:偶发 NPE
152+
// 在修复后:稳定通过
153+
assertThat(converse.offer("1+2").await().text())
154+
.isEqualTo("2+2=4\n2+3=5\n1+2=");
155+
}
156+
100157
@Test
101158
@DisplayName("测试 Retriever")
102159
void shouldOkWhenAiFlowWithRetriever() {

0 commit comments

Comments
 (0)