Skip to content

Commit 803f3d7

Browse files
authored
Improve multi-server connection testing (#3170)
Add tests that assert on the returned statements from the multi-server-connection Change connection logic to keep same current selector on read operations Resolves #3094
1 parent 999f23b commit 803f3d7

File tree

2 files changed

+122
-42
lines changed

2 files changed

+122
-42
lines changed

yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/MultiServerConnectionFactory.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,12 @@ public int getCurrentConnectionSelector() {
174174

175175
@Override
176176
public RelationalStatement createStatement() throws SQLException {
177-
return getNextConnection("createStatement").createStatement();
177+
return getCurrentConnection(true, "createStatement").createStatement();
178178
}
179179

180180
@Override
181181
public RelationalPreparedStatement prepareStatement(String sql) throws SQLException {
182-
return getNextConnection("prepareStatement").prepareStatement(sql);
182+
return getCurrentConnection(true, "prepareStatement").prepareStatement(sql);
183183
}
184184

185185
@Override
@@ -195,7 +195,7 @@ public void setAutoCommit(boolean autoCommit) throws SQLException {
195195

196196
@Override
197197
public boolean getAutoCommit() throws SQLException {
198-
return getNextConnection("getAutoCommit").getAutoCommit();
198+
return getCurrentConnection(false, "getAutoCommit").getAutoCommit();
199199
}
200200

201201
@Override
@@ -218,32 +218,35 @@ public void close() throws SQLException {
218218

219219
@Override
220220
public boolean isClosed() throws SQLException {
221-
return getNextConnection("isClosed").isClosed();
221+
return getCurrentConnection(false, "isClosed").isClosed();
222222
}
223223

224224
@Override
225225
public DatabaseMetaData getMetaData() throws SQLException {
226-
return getNextConnection("getMetaData").getMetaData();
226+
return getCurrentConnection(false, "getMetaData").getMetaData();
227227
}
228228

229229
@Override
230230
public void setTransactionIsolation(int level) throws SQLException {
231-
getNextConnection("setTransactionIsolation").setTransactionIsolation(level);
231+
logger.info("Sending operation {} to all connections", "setTransactionIsolation");
232+
for (RelationalConnection connection : relationalConnections) {
233+
connection.setTransactionIsolation(level);
234+
}
232235
}
233236

234237
@Override
235238
public int getTransactionIsolation() throws SQLException {
236-
return getNextConnection("getTransactionIsolation").getTransactionIsolation();
239+
return getCurrentConnection(false, "getTransactionIsolation").getTransactionIsolation();
237240
}
238241

239242
@Override
240243
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
241-
return getNextConnection("createArrayOf").createArrayOf(typeName, elements);
244+
return getCurrentConnection(true, "createArrayOf").createArrayOf(typeName, elements);
242245
}
243246

244247
@Override
245248
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
246-
return getNextConnection("createStruct").createStruct(typeName, attributes);
249+
return getCurrentConnection(true, "createStruct").createStruct(typeName, attributes);
247250
}
248251

249252
@Override
@@ -256,13 +259,13 @@ public void setSchema(String schema) throws SQLException {
256259

257260
@Override
258261
public String getSchema() throws SQLException {
259-
return getNextConnection("getSchema").getSchema();
262+
return getCurrentConnection(false, "getSchema").getSchema();
260263
}
261264

262265
@Nonnull
263266
@Override
264267
public Options getOptions() {
265-
return getNextConnection("getOptions").getOptions();
268+
return getCurrentConnection(false, "getOptions").getOptions();
266269
}
267270

268271
@Override
@@ -275,10 +278,19 @@ public void setOption(Options.Name name, Object value) throws SQLException {
275278

276279
@Override
277280
public URI getPath() {
278-
return getNextConnection("getPath").getPath();
281+
return getCurrentConnection(false, "getPath").getPath();
279282
}
280283

281-
private RelationalConnection getNextConnection(String op) {
284+
/**
285+
* Get the connection to send requests to.
286+
* This method conditionally advances the connection selector. This is done for ease of testing, where some
287+
* commands that just return information will not advance the selector, leading to better predictability
288+
* of the command routing.
289+
* @param advance whether to advance the connection selector
290+
* @param op the name of the operation (for logging)
291+
* @return the underlying connection to use
292+
*/
293+
private RelationalConnection getCurrentConnection(boolean advance, String op) {
282294
switch (connectionSelectionPolicy) {
283295
case DEFAULT:
284296
if (logger.isInfoEnabled()) {
@@ -290,7 +302,9 @@ private RelationalConnection getNextConnection(String op) {
290302
if (logger.isInfoEnabled()) {
291303
logger.info("Sending operation {} to connection {}", op, currentConnectionSelector);
292304
}
293-
currentConnectionSelector = (currentConnectionSelector + 1) % relationalConnections.size();
305+
if (advance) {
306+
currentConnectionSelector = (currentConnectionSelector + 1) % relationalConnections.size();
307+
}
294308
return result;
295309
default:
296310
throw new IllegalStateException("Unsupported selection policy " + connectionSelectionPolicy);

yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020

2121
import com.apple.foundationdb.relational.api.RelationalConnection;
22+
import com.apple.foundationdb.relational.api.RelationalPreparedStatement;
2223
import com.apple.foundationdb.relational.yamltests.MultiServerConnectionFactory;
2324
import com.apple.foundationdb.relational.yamltests.YamlRunner;
2425
import org.junit.jupiter.api.Test;
@@ -42,61 +43,110 @@ void testDefaultPolicy(int initialConnection) throws SQLException {
4243
MultiServerConnectionFactory classUnderTest = new MultiServerConnectionFactory(
4344
MultiServerConnectionFactory.ConnectionSelectionPolicy.DEFAULT,
4445
initialConnection,
45-
dummyConnectionFactory(),
46-
List.of(dummyConnectionFactory()));
47-
assertEquals(initialConnection, classUnderTest.getCurrentConnectionSelector());
48-
MultiServerConnectionFactory.MultiServerRelationalConnection connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
49-
assertEquals(0, connection.getCurrentConnectionSelector());
50-
assertEquals(initialConnection, classUnderTest.getCurrentConnectionSelector());
46+
dummyConnectionFactory("Primary"),
47+
List.of(dummyConnectionFactory("Alternate")));
48+
49+
MultiServerConnectionFactory.MultiServerRelationalConnection connection =
50+
(MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
51+
assertConnection(connection, "Primary");
52+
assertStatement(connection.prepareStatement("SQL"), "Primary");
53+
assertStatement(connection.prepareStatement("SQL"), "Primary");
54+
5155
connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
52-
assertEquals(0, connection.getCurrentConnectionSelector());
53-
assertEquals(initialConnection, classUnderTest.getCurrentConnectionSelector());
56+
assertConnection(connection, "Primary");
57+
assertStatement(connection.prepareStatement("SQL"), "Primary");
58+
assertStatement(connection.prepareStatement("SQL"), "Primary");
59+
5460
connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
55-
assertEquals(0, connection.getCurrentConnectionSelector());
56-
assertEquals(initialConnection, classUnderTest.getCurrentConnectionSelector());
61+
assertConnection(connection, "Primary");
62+
assertStatement(connection.prepareStatement("SQL"), "Primary");
63+
assertStatement(connection.prepareStatement("SQL"), "Primary");
5764
}
5865

5966
@ParameterizedTest
6067
@CsvSource({"0", "1"})
6168
void testAlternatePolicy(int initialConnection) throws SQLException {
69+
final String[] path = new String[] { "Primary", "Alternate" };
70+
final String initialConnectionName = path[initialConnection];
71+
final String otherConnectionName = path[(initialConnection + 1) % 2];
72+
6273
MultiServerConnectionFactory classUnderTest = new MultiServerConnectionFactory(
6374
MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
6475
initialConnection,
65-
dummyConnectionFactory(),
66-
List.of(dummyConnectionFactory()));
67-
assertEquals(initialConnection, classUnderTest.getCurrentConnectionSelector());
68-
MultiServerConnectionFactory.MultiServerRelationalConnection connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
69-
assertEquals(initialConnection % 2, connection.getCurrentConnectionSelector());
70-
assertEquals((initialConnection + 1) % 2, classUnderTest.getCurrentConnectionSelector());
76+
dummyConnectionFactory("Primary"),
77+
List.of(dummyConnectionFactory("Alternate")));
78+
79+
// First run:
80+
// - Factory current connection: initial connection
81+
// - connection current connection: initial connection
82+
// - statement: initial connection (2 statements)
83+
MultiServerConnectionFactory.MultiServerRelationalConnection connection =
84+
(MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
85+
assertConnection(connection, initialConnectionName);
86+
assertStatement(connection.prepareStatement("SQL"), initialConnectionName);
87+
// next statement
88+
assertStatement(connection.prepareStatement("SQL"), otherConnectionName);
89+
90+
// Second run:
91+
// - Factory current connection: alternate connection
92+
// - connection current connection: alternate connection
93+
// - statement: alternate connection (2 statements)
94+
connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
95+
assertConnection(connection, otherConnectionName);
96+
assertStatement(connection.prepareStatement("SQL"), otherConnectionName);
97+
// next statement
98+
assertStatement(connection.prepareStatement("SQL"), initialConnectionName);
99+
100+
// Third run:
101+
// - Factory current connection: initial connection
102+
// - connection current connection: initial connection
103+
// - statement: initial connection (1 statement)
71104
connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
72-
assertEquals((initialConnection + 1) % 2, connection.getCurrentConnectionSelector());
73-
assertEquals(initialConnection % 2, classUnderTest.getCurrentConnectionSelector());
105+
assertConnection(connection, initialConnectionName);
106+
// just one statement for this connection
107+
assertStatement(connection.prepareStatement("SQL"), initialConnectionName);
108+
109+
// Fourth run:
110+
// - Factory current connection: alternate connection
111+
// - connection current connection: alternate connection
112+
// - statement: alternate connection (3 statements)
74113
connection = (MultiServerConnectionFactory.MultiServerRelationalConnection)classUnderTest.getNewConnection(URI.create("Blah"));
75-
assertEquals(initialConnection % 2, connection.getCurrentConnectionSelector());
76-
assertEquals((initialConnection + 1) % 2, classUnderTest.getCurrentConnectionSelector());
114+
assertConnection(connection, otherConnectionName);
115+
assertStatement(connection.prepareStatement("SQL"), otherConnectionName);
116+
// next statements
117+
assertStatement(connection.prepareStatement("SQL"), initialConnectionName);
118+
assertStatement(connection.prepareStatement("SQL"), otherConnectionName);
77119
}
78120

79121
@Test
80122
void testIllegalInitialConnection() {
81123
assertThrows(AssertionError.class, () -> new MultiServerConnectionFactory(
82124
MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
83125
-1,
84-
dummyConnectionFactory(),
85-
List.of(dummyConnectionFactory())));
126+
dummyConnectionFactory("A"),
127+
List.of(dummyConnectionFactory("B"))));
86128
assertThrows(AssertionError.class, () -> new MultiServerConnectionFactory(
87129
MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
88130
7,
89-
dummyConnectionFactory(),
90-
List.of(dummyConnectionFactory())));
131+
dummyConnectionFactory("A"),
132+
List.of(dummyConnectionFactory("B"))));
91133
}
92134

93-
YamlRunner.YamlConnectionFactory dummyConnectionFactory() {
135+
private void assertStatement(final RelationalPreparedStatement statement, final String query) throws SQLException {
136+
assertEquals("name=" + query, ((RelationalConnection)statement.getConnection()).getPath().getQuery());
137+
}
138+
139+
private static void assertConnection(final MultiServerConnectionFactory.MultiServerRelationalConnection connection, final String query) {
140+
assertEquals("name=" + query, connection.getPath().getQuery());
141+
}
142+
143+
YamlRunner.YamlConnectionFactory dummyConnectionFactory(@Nonnull String name) {
94144
return new YamlRunner.YamlConnectionFactory() {
95145
@Override
96146
public RelationalConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
97-
final RelationalConnection connection = Mockito.mock(RelationalConnection.class);
98-
Mockito.when(connection.unwrap(RelationalConnection.class)).thenReturn(connection);
99-
return connection;
147+
// Add query string to connection so we can tell where it came from
148+
URI newPath = URI.create(connectPath + "?name=" + name);
149+
return dummyConnection(newPath);
100150
}
101151

102152
@Override
@@ -105,4 +155,20 @@ public Set<String> getVersionsUnderTest() {
105155
}
106156
};
107157
}
158+
159+
@Nonnull
160+
private static RelationalConnection dummyConnection(@Nonnull URI connectPath) throws SQLException {
161+
final RelationalConnection connection = Mockito.mock(RelationalConnection.class);
162+
Mockito.when(connection.unwrap(RelationalConnection.class)).thenReturn(connection);
163+
Mockito.when(connection.getPath()).thenReturn(connectPath);
164+
final RelationalPreparedStatement statement = dummyPreparedStatement(connection);
165+
Mockito.when(connection.prepareStatement(Mockito.anyString())).thenReturn(statement);
166+
return connection;
167+
}
168+
169+
private static RelationalPreparedStatement dummyPreparedStatement(final RelationalConnection connection) throws SQLException {
170+
final RelationalPreparedStatement statement = Mockito.mock(RelationalPreparedStatement.class);
171+
Mockito.when(statement.getConnection()).thenReturn(connection);
172+
return statement;
173+
}
108174
}

0 commit comments

Comments
 (0)