Skip to content

Commit 40ab549

Browse files
committed
Append tests for the RuleExecutor and the ParameterizedRuleExecutor
1 parent e2b5837 commit 40ab549

File tree

3 files changed

+841
-0
lines changed

3 files changed

+841
-0
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.rule;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.test.ESTestCase;
13+
import org.elasticsearch.xpack.esql.core.tree.Node;
14+
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
15+
import org.elasticsearch.xpack.esql.core.tree.Source;
16+
17+
import java.io.IOException;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.concurrent.CountDownLatch;
21+
import java.util.concurrent.TimeUnit;
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
import java.util.concurrent.atomic.AtomicReference;
24+
25+
import static org.hamcrest.Matchers.containsString;
26+
27+
/**
28+
* Abstract base class for rule execution tests providing common test infrastructure.
29+
*/
30+
public abstract class AbstractRuleTestCase extends ESTestCase {
31+
32+
// Test node implementation
33+
protected static class TestNode extends Node<TestNode> {
34+
private final String value;
35+
private final List<TestNode> children;
36+
37+
public TestNode(String value) {
38+
this(Source.EMPTY, value, Collections.emptyList());
39+
}
40+
41+
public TestNode(String value, List<TestNode> children) {
42+
this(Source.EMPTY, value, children);
43+
}
44+
45+
public TestNode(Source source, String value, List<TestNode> children) {
46+
super(source, children);
47+
this.value = value;
48+
this.children = children;
49+
}
50+
51+
public String value() {
52+
return value;
53+
}
54+
55+
@Override
56+
public TestNode replaceChildren(List<TestNode> newChildren) {
57+
return new TestNode(source(), value, newChildren);
58+
}
59+
60+
@Override
61+
protected NodeInfo<TestNode> info() {
62+
return NodeInfo.create(this, TestNode::new, value, children);
63+
}
64+
65+
@Override
66+
public void writeTo(StreamOutput out) throws IOException {
67+
// Not needed for tests
68+
}
69+
70+
@Override
71+
public String getWriteableName() {
72+
return "test-node";
73+
}
74+
75+
@Override
76+
public boolean equals(Object obj) {
77+
if (this == obj) return true;
78+
if (!(obj instanceof TestNode)) return false;
79+
TestNode other = (TestNode) obj;
80+
return value.equals(other.value) && children.equals(other.children);
81+
}
82+
83+
@Override
84+
public int hashCode() {
85+
return value.hashCode() * 31 + children.hashCode();
86+
}
87+
88+
@Override
89+
public String toString() {
90+
return value + (children.isEmpty() ? "" : "(" + children + ")");
91+
}
92+
}
93+
94+
// Test rule implementations
95+
protected static class AppendRule extends Rule.Sync<TestNode, TestNode> {
96+
private final String suffix;
97+
98+
public AppendRule(String suffix) {
99+
this.suffix = suffix;
100+
}
101+
102+
@Override
103+
public TestNode apply(TestNode node) {
104+
return new TestNode(node.value() + suffix, node.children());
105+
}
106+
107+
@Override
108+
public String name() {
109+
return "Append" + suffix;
110+
}
111+
}
112+
113+
protected static class ConditionalRule extends Rule.Sync<TestNode, TestNode> {
114+
private final String trigger;
115+
private final String replacement;
116+
117+
public ConditionalRule(String trigger, String replacement) {
118+
this.trigger = trigger;
119+
this.replacement = replacement;
120+
}
121+
122+
@Override
123+
public TestNode apply(TestNode node) {
124+
if (node.value().equals(trigger)) {
125+
return new TestNode(replacement, node.children());
126+
}
127+
return node; // No change if condition not met
128+
}
129+
130+
@Override
131+
public String name() {
132+
return "Conditional" + trigger + "To" + replacement;
133+
}
134+
}
135+
136+
protected static class CountingAsyncRule extends Rule.Async<TestNode, TestNode> {
137+
private final AtomicInteger callCount = new AtomicInteger(0);
138+
private final String suffix;
139+
140+
public CountingAsyncRule(String suffix) {
141+
this.suffix = suffix;
142+
}
143+
144+
@Override
145+
public void apply(TestNode node, ActionListener<TestNode> listener) {
146+
callCount.incrementAndGet();
147+
// Simulate async processing
148+
listener.onResponse(new TestNode(node.value() + suffix, node.children()));
149+
}
150+
151+
@Override
152+
public String name() {
153+
return "CountingAsync" + suffix;
154+
}
155+
156+
public int getCallCount() {
157+
return callCount.get();
158+
}
159+
}
160+
161+
protected static class FailingRule extends Rule.Async<TestNode, TestNode> {
162+
private final String errorMessage;
163+
164+
public FailingRule(String errorMessage) {
165+
this.errorMessage = errorMessage;
166+
}
167+
168+
@Override
169+
public void apply(TestNode node, ActionListener<TestNode> listener) {
170+
listener.onFailure(new RuntimeException(errorMessage));
171+
}
172+
173+
@Override
174+
public String name() {
175+
return "FailingRule";
176+
}
177+
}
178+
179+
protected static class TestParameterizedRule extends ParameterizedRule.Sync<TestNode, TestNode, String> {
180+
@Override
181+
public TestNode apply(TestNode node, String param) {
182+
return new TestNode(node.value() + "_" + param, node.children());
183+
}
184+
185+
@Override
186+
public String name() {
187+
return "TestParameterizedRule";
188+
}
189+
}
190+
191+
protected static class TestContextParameterizedRule extends ParameterizedRule.Sync<TestNode, TestNode, TestContext> {
192+
@Override
193+
public TestNode apply(TestNode node, TestContext context) {
194+
return new TestNode(context.prefix + node.value() + context.suffix, node.children());
195+
}
196+
197+
@Override
198+
public String name() {
199+
return "TestContextParameterizedRule";
200+
}
201+
}
202+
203+
// Test context class
204+
protected static class TestContext {
205+
public final String prefix;
206+
public final String suffix;
207+
208+
public TestContext(String prefix, String suffix) {
209+
this.prefix = prefix;
210+
this.suffix = suffix;
211+
}
212+
213+
@Override
214+
public String toString() {
215+
return prefix + "..." + suffix;
216+
}
217+
}
218+
219+
// Helper methods for async testing
220+
protected static class AsyncResult<T> {
221+
private final CountDownLatch latch = new CountDownLatch(1);
222+
private final AtomicReference<T> result = new AtomicReference<>();
223+
private final AtomicReference<Throwable> error = new AtomicReference<>();
224+
225+
public ActionListener<T> listener() {
226+
return ActionListener.wrap(
227+
res -> {
228+
result.set(res);
229+
latch.countDown();
230+
},
231+
err -> {
232+
error.set(err);
233+
latch.countDown();
234+
}
235+
);
236+
}
237+
238+
public boolean await() throws InterruptedException {
239+
return latch.await(5, TimeUnit.SECONDS);
240+
}
241+
242+
public T get() {
243+
return result.get();
244+
}
245+
246+
public Throwable getError() {
247+
return error.get();
248+
}
249+
250+
public void assertSuccess() {
251+
try {
252+
assertTrue("Execution should complete within timeout", await());
253+
} catch (InterruptedException e) {
254+
Thread.currentThread().interrupt();
255+
throw new RuntimeException(e);
256+
}
257+
assertNull("Should not have error: " + getError(), getError());
258+
assertNotNull("Should have result", get());
259+
}
260+
261+
public void assertFailure() {
262+
try {
263+
assertTrue("Execution should complete within timeout", await());
264+
} catch (InterruptedException e) {
265+
Thread.currentThread().interrupt();
266+
throw new RuntimeException(e);
267+
}
268+
assertNotNull("Should have error", getError());
269+
assertNull("Should not have result", get());
270+
}
271+
272+
public void assertFailure(String expectedMessage) {
273+
assertFailure();
274+
assertThat(getError().getMessage(), containsString(expectedMessage));
275+
}
276+
}
277+
278+
// Utility method to await async results
279+
protected static boolean await(CountDownLatch latch) {
280+
try {
281+
return latch.await(5, TimeUnit.SECONDS);
282+
} catch (InterruptedException e) {
283+
Thread.currentThread().interrupt();
284+
throw new RuntimeException(e);
285+
}
286+
}
287+
}

0 commit comments

Comments
 (0)