Skip to content

Commit cfb284e

Browse files
committed
Add basic tests for PlannerRepl
1 parent 7ccc884 commit cfb284e

File tree

3 files changed

+426
-0
lines changed

3 files changed

+426
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* CommandsTest.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.debug;
22+
23+
import com.apple.foundationdb.record.query.plan.cascades.PlanContext;
24+
import com.apple.foundationdb.record.query.plan.cascades.PlannerPhase;
25+
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
26+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
27+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
28+
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
29+
import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue;
30+
import org.jline.terminal.Terminal;
31+
import org.jline.terminal.TerminalBuilder;
32+
import org.junit.jupiter.api.BeforeEach;
33+
import org.junit.jupiter.api.Test;
34+
35+
import java.io.ByteArrayOutputStream;
36+
import java.io.IOException;
37+
import java.io.PipedInputStream;
38+
import java.io.PipedOutputStream;
39+
import java.nio.charset.StandardCharsets;
40+
import java.util.ArrayDeque;
41+
import java.util.Collections;
42+
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
45+
46+
47+
class CommandsTest {
48+
private String query;
49+
private PipedOutputStream outIn;
50+
private Terminal terminal;
51+
private ByteArrayOutputStream outputStream;
52+
private PlannerRepl debugger;
53+
54+
@BeforeEach
55+
void setUp() throws IOException {
56+
query = "SELECT * FROM A";
57+
PipedInputStream in = new PipedInputStream();
58+
outIn = new PipedOutputStream(in);
59+
outputStream = new ByteArrayOutputStream(2048);
60+
terminal = TerminalBuilder.builder().streams(in, outputStream).build();
61+
debugger = new PlannerRepl(terminal, false);
62+
63+
Debugger.setDebugger(debugger);
64+
Debugger.setup();
65+
Debugger.withDebugger(d -> d.onQuery(query, PlanContext.EMPTY_CONTEXT));
66+
}
67+
68+
@Test
69+
void testBreakCommand() throws IOException {
70+
outIn.write("break rule somerule BEGIN\nbreak list\ncont\n".getBytes(StandardCharsets.UTF_8));
71+
72+
StatsDebugger.withDebugger(
73+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
74+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.BEGIN))
75+
);
76+
77+
terminal.writer().close();
78+
assertThat(outputStream.toString()).contains(
79+
ReplTestUtil.coloredKeyValue("id", "0"),
80+
ReplTestUtil.coloredKeyValue("kind", "OnRuleBreakPoint"),
81+
ReplTestUtil.coloredKeyValue("location", "BEGIN"),
82+
ReplTestUtil.coloredKeyValue("ruleNamePrefix", "somerule")
83+
);
84+
}
85+
86+
@Test
87+
void testCurrentCommand() throws IOException {
88+
outIn.write("current\ncont\n".getBytes(StandardCharsets.UTF_8));
89+
90+
StatsDebugger.withDebugger(
91+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
92+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.BEGIN))
93+
);
94+
95+
terminal.writer().close();
96+
assertThat(outputStream.toString()).contains(
97+
ReplTestUtil.coloredKeyValue("event", "initphase"),
98+
ReplTestUtil.coloredKeyValue("description", "initiating planner phase")
99+
);
100+
}
101+
102+
@Test
103+
void testEventsCommand() throws IOException {
104+
outIn.write("step 1\nstep 1\nevents\ncont\n".getBytes(StandardCharsets.UTF_8));
105+
final RelationalExpression exp1 = new SelectExpression(
106+
LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList());
107+
108+
StatsDebugger.withDebugger(
109+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
110+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.BEGIN))
111+
);
112+
StatsDebugger.withDebugger(
113+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
114+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.END))
115+
);
116+
StatsDebugger.withDebugger(
117+
d -> d.onEvent(Debugger.InsertIntoMemoEvent.newExp(exp1))
118+
);
119+
120+
terminal.writer().close();
121+
assertThat(outputStream.toString()).contains(
122+
String.join(
123+
"; ",
124+
ReplTestUtil.coloredKeyValue("tick", "0"),
125+
ReplTestUtil.coloredKeyValue("shorthand", "initphase")
126+
),
127+
String.join(
128+
"; ",
129+
ReplTestUtil.coloredKeyValue("tick", "1"),
130+
ReplTestUtil.coloredKeyValue("shorthand", "initphase")
131+
),
132+
String.join(
133+
"; ",
134+
ReplTestUtil.coloredKeyValue("tick", "2"),
135+
ReplTestUtil.coloredKeyValue("shorthand", "insert_into_memo")
136+
)
137+
);
138+
}
139+
140+
@Test
141+
void testExpsCommand() throws IOException {
142+
outIn.write("exps\ncont\n".getBytes(StandardCharsets.UTF_8));
143+
final RelationalExpression exp0 = new SelectExpression(
144+
LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList());
145+
final RelationalExpression exp1 = new SelectExpression(
146+
LiteralValue.ofScalar(2), Collections.emptyList(), Collections.emptyList());
147+
final Reference ref0 = Reference.initialOf(exp0, exp1);
148+
149+
StatsDebugger.withDebugger(
150+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
151+
PlannerPhase.REWRITING, ref0, new ArrayDeque<>(), Debugger.Location.BEGIN))
152+
);
153+
154+
terminal.writer().close();
155+
assertThat(outputStream.toString()).contains(
156+
ReplTestUtil.coloredKeyValue("id", debugger.nameForObject(exp0)),
157+
ReplTestUtil.coloredKeyValue("id", debugger.nameForObject(exp0))
158+
);
159+
}
160+
161+
@Test
162+
void testRefsCommand() throws IOException {
163+
outIn.write("refs\ncont\n".getBytes(StandardCharsets.UTF_8));
164+
final RelationalExpression exp0 = new SelectExpression(
165+
LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList());
166+
final RelationalExpression exp1 = new SelectExpression(
167+
LiteralValue.ofScalar(2), Collections.emptyList(), Collections.emptyList());
168+
final Reference ref0 = Reference.initialOf(exp0, exp1);
169+
170+
StatsDebugger.withDebugger(
171+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
172+
PlannerPhase.REWRITING, ref0, new ArrayDeque<>(), Debugger.Location.BEGIN))
173+
);
174+
175+
terminal.writer().close();
176+
assertThat(outputStream.toString()).contains(
177+
ReplTestUtil.coloredKeyValue("id", debugger.nameForObject(ref0)),
178+
ReplTestUtil.coloredKeyValue(
179+
"members", "{" + debugger.nameForObject(exp0) + ", " + debugger.nameForObject(exp1) + "}"
180+
)
181+
);
182+
}
183+
184+
@Test
185+
void testQunsCommand() throws IOException {
186+
outIn.write("quns\ncont\n".getBytes(StandardCharsets.UTF_8));
187+
final Reference ref0 = Reference.empty();
188+
final Quantifier qun = Quantifier.forEach(ref0);
189+
190+
StatsDebugger.withDebugger(
191+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
192+
PlannerPhase.REWRITING, ref0, new ArrayDeque<>(), Debugger.Location.BEGIN))
193+
);
194+
195+
terminal.writer().close();
196+
assertThat(outputStream.toString()).contains(
197+
ReplTestUtil.coloredKeyValue("id", debugger.nameForObject(qun)),
198+
ReplTestUtil.coloredKeyValue("ranges over", debugger.nameForObject(ref0))
199+
);
200+
}
201+
202+
@Test
203+
void testRestartCommand() throws IOException {
204+
outIn.write("restart\n".getBytes(StandardCharsets.UTF_8));
205+
final EventState eventStateBeforeRestart = debugger.getCurrentState();
206+
final SymbolTables symbolStateBeforeRestart = debugger.getCurrentSymbolState();
207+
208+
assertThatThrownBy(() -> StatsDebugger.withDebugger(
209+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
210+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.BEGIN)))
211+
).isInstanceOf(RestartException.class);
212+
213+
assertThat(debugger.getCurrentState()).isNotSameAs(eventStateBeforeRestart);
214+
assertThat(debugger.getCurrentSymbolState()).isNotSameAs(symbolStateBeforeRestart);
215+
}
216+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* PlannerReplTest.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.record.query.plan.cascades.debug;
22+
23+
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
24+
import com.apple.foundationdb.record.query.plan.cascades.PlanContext;
25+
import com.apple.foundationdb.record.query.plan.cascades.PlannerPhase;
26+
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
27+
import com.apple.foundationdb.record.query.plan.cascades.Reference;
28+
import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
29+
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
30+
import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue;
31+
32+
import org.jline.terminal.Terminal;
33+
import org.jline.terminal.TerminalBuilder;
34+
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.api.Test;
36+
37+
import java.io.ByteArrayOutputStream;
38+
import java.io.IOException;
39+
import java.io.PipedInputStream;
40+
import java.io.PipedOutputStream;
41+
import java.nio.charset.StandardCharsets;
42+
import java.util.ArrayDeque;
43+
import java.util.Collections;
44+
45+
import static org.assertj.core.api.Assertions.assertThat;
46+
47+
class PlannerReplTest {
48+
private PipedOutputStream outIn;
49+
private Terminal terminal;
50+
private ByteArrayOutputStream outputStream;
51+
private PlannerRepl debugger;
52+
53+
@BeforeEach
54+
void setUp() throws IOException {
55+
PipedInputStream in = new PipedInputStream();
56+
outIn = new PipedOutputStream(in);
57+
outputStream = new ByteArrayOutputStream(2048);
58+
terminal = TerminalBuilder.builder().streams(in, outputStream).build();
59+
debugger = new PlannerRepl(terminal, false);
60+
61+
Debugger.setDebugger(debugger);
62+
Debugger.setup();
63+
Debugger.withDebugger(d -> d.onQuery("SELECT * FROM A", PlanContext.EMPTY_CONTEXT));
64+
}
65+
66+
@Test
67+
void testOnQueryPrintsQueryAndCreatesNewState() throws IOException, InterruptedException {
68+
outIn.write("cont\n".getBytes(StandardCharsets.UTF_8));
69+
final String query = "SELECT * FROM B";
70+
final EventState eventStatePreQuery = debugger.getCurrentState();
71+
final SymbolTables symbolStatePreQuery = debugger.getCurrentSymbolState();
72+
73+
Debugger.withDebugger(d -> d.onQuery(query, PlanContext.EMPTY_CONTEXT));
74+
75+
terminal.writer().close();
76+
assertThat(outputStream.toString()).contains(ReplTestUtil.coloredKeyValue("query", query));
77+
assertThat(debugger.getCurrentState()).isNotSameAs(eventStatePreQuery);
78+
assertThat(debugger.getCurrentSymbolState()).isNotSameAs(symbolStatePreQuery);
79+
}
80+
81+
@Test
82+
void testRestartResetsState() throws IOException {
83+
outIn.write("cont\n".getBytes(StandardCharsets.UTF_8));
84+
final EventState eventStatePreQuery = debugger.getCurrentState();
85+
final SymbolTables symbolStatePreQuery = debugger.getCurrentSymbolState();
86+
87+
debugger.restartState();
88+
89+
assertThat(debugger.getCurrentState()).isNotSameAs(eventStatePreQuery);
90+
assertThat(debugger.getCurrentSymbolState()).isNotSameAs(symbolStatePreQuery);
91+
}
92+
93+
@Test
94+
void testOnEventUpdatesStatsMap() throws IOException {
95+
outIn.write("cont\n".getBytes(StandardCharsets.UTF_8));
96+
97+
StatsDebugger.withDebugger(
98+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
99+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.BEGIN))
100+
);
101+
StatsDebugger.withDebugger(
102+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
103+
PlannerPhase.REWRITING, Reference.empty(), new ArrayDeque<>(), Debugger.Location.END))
104+
);
105+
StatsDebugger.withDebugger(
106+
d -> d.onEvent(Debugger.InsertIntoMemoEvent.newExp(new SelectExpression(LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList())))
107+
);
108+
109+
assertThat(debugger.getCurrentState().getEvents()).hasSize(3);
110+
assertThat(debugger.getCurrentState().getEventProtos()).hasSize(3);
111+
assertThat(debugger.getStatsMaps()).isNotEmpty();
112+
assertThat(debugger.getStatsMaps().get().getEventWithStateClassStatsMapByPlannerPhase(PlannerPhase.REWRITING))
113+
.hasValueSatisfying(
114+
m -> assertThat(m).hasSize(1)
115+
.containsKey(Debugger.InitiatePlannerPhaseEvent.class)
116+
);
117+
assertThat(debugger.getStatsMaps().get().getEventWithoutStateClassStatsMap()).hasSize(1)
118+
.containsKey(Debugger.InsertIntoMemoEvent.class);
119+
}
120+
121+
@Test
122+
void testGetIndex() throws IOException {
123+
outIn.write("cont\n".getBytes(StandardCharsets.UTF_8));
124+
final RelationalExpression exp0 = new SelectExpression(
125+
LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList());
126+
final RelationalExpression exp1 = new SelectExpression(
127+
LiteralValue.ofScalar(2), Collections.emptyList(), Collections.emptyList());
128+
final Reference ref0 = Reference.initialOf(exp0, exp1);
129+
debugger.onRegisterQuantifier(Quantifier.forEach(ref0, CorrelationIdentifier.of("0")));
130+
131+
StatsDebugger.withDebugger(
132+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
133+
PlannerPhase.REWRITING, ref0, new ArrayDeque<>(), Debugger.Location.BEGIN))
134+
);
135+
136+
assertThat(SymbolDebugger.mapDebugger(d -> d.onGetIndex(RelationalExpression.class))).hasValue(2);
137+
assertThat(SymbolDebugger.mapDebugger(d -> d.onGetIndex(Reference.class))).hasValue(1);
138+
assertThat(SymbolDebugger.mapDebugger(d -> d.onGetIndex(Quantifier.class))).hasValue(1);
139+
}
140+
141+
@Test
142+
void testUpdateIndex() {
143+
assertThat(SymbolDebugger.mapDebugger(d -> d.onGetIndex(RelationalExpression.class))).hasValue(0);
144+
145+
SymbolDebugger.withDebugger(d -> d.onUpdateIndex(RelationalExpression.class, (i) -> i + 1));
146+
147+
assertThat(SymbolDebugger.mapDebugger(d -> d.onGetIndex(RelationalExpression.class))).hasValue(1);
148+
}
149+
150+
@Test
151+
void testPrintIdentifiers() throws IOException {
152+
outIn.write("exp0\nexp1\nref0\nqun0\ncont\n".getBytes(StandardCharsets.UTF_8));
153+
var exp0 = new SelectExpression(LiteralValue.ofScalar(1), Collections.emptyList(), Collections.emptyList());
154+
var exp1 = new SelectExpression(LiteralValue.ofScalar(2), Collections.emptyList(), Collections.emptyList());
155+
var ref0 = Reference.initialOf(exp0, exp1);
156+
var qun0 = Quantifier.forEach(ref0, CorrelationIdentifier.of("0"));
157+
158+
StatsDebugger.withDebugger(
159+
d -> d.onEvent(new Debugger.InitiatePlannerPhaseEvent(
160+
PlannerPhase.REWRITING, ref0, new ArrayDeque<>(), Debugger.Location.BEGIN))
161+
);
162+
163+
terminal.writer().close();
164+
assertThat(outputStream.toString()).contains(
165+
ReplTestUtil.coloredKeyValue("name", debugger.nameForObject(exp0)),
166+
ReplTestUtil.coloredKeyValue("name", debugger.nameForObject(exp1)),
167+
ReplTestUtil.coloredKeyValue("name", debugger.nameForObject(ref0)),
168+
ReplTestUtil.coloredKeyValue("name", debugger.nameForObject(qun0))
169+
);
170+
}
171+
}

0 commit comments

Comments
 (0)