Skip to content

Commit f534f3a

Browse files
authored
test: add tests for using multiplexed sessions (#1934)
Add a couple of tests to verify that multiplexed sessions are being used by the JDBC driver when the environment variable has been set.
1 parent 98eb542 commit f534f3a

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner.jdbc;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.assertTrue;
22+
import static org.junit.Assume.assumeFalse;
23+
import static org.junit.Assume.assumeTrue;
24+
25+
import com.google.cloud.spanner.MockServerHelper;
26+
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
27+
import com.google.cloud.spanner.connection.AbstractMockServerTest;
28+
import com.google.common.base.Strings;
29+
import com.google.protobuf.ListValue;
30+
import com.google.protobuf.Value;
31+
import com.google.spanner.v1.ExecuteSqlRequest;
32+
import com.google.spanner.v1.ResultSetMetadata;
33+
import com.google.spanner.v1.ResultSetStats;
34+
import com.google.spanner.v1.StructType;
35+
import com.google.spanner.v1.StructType.Field;
36+
import com.google.spanner.v1.Type;
37+
import com.google.spanner.v1.TypeCode;
38+
import java.sql.Connection;
39+
import java.sql.DriverManager;
40+
import java.sql.ResultSet;
41+
import java.sql.SQLException;
42+
import java.sql.Statement;
43+
import java.util.Collections;
44+
import java.util.Map;
45+
import org.junit.AfterClass;
46+
import org.junit.Before;
47+
import org.junit.BeforeClass;
48+
import org.junit.Test;
49+
import org.junit.runner.RunWith;
50+
import org.junit.runners.JUnit4;
51+
52+
@RunWith(JUnit4.class)
53+
public class MultiplexedSessionsTest extends AbstractMockServerTest {
54+
private static final String PROCESS_ENVIRONMENT = "java.lang.ProcessEnvironment";
55+
private static final String ENVIRONMENT = "theUnmodifiableEnvironment";
56+
private static final String SOURCE_MAP = "m";
57+
private static final Object STATIC_METHOD = null;
58+
private static final Class<?> UMODIFIABLE_MAP_CLASS =
59+
Collections.unmodifiableMap(Collections.emptyMap()).getClass();
60+
private static final Class<?> MAP_CLASS = Map.class;
61+
62+
private static boolean setEnvVar = false;
63+
64+
private String query;
65+
private String dml;
66+
private String dmlReturning;
67+
68+
@SuppressWarnings("unchecked")
69+
private static Map<String, String> getModifiableEnvironment() throws Exception {
70+
Class<?> environmentClass = Class.forName(PROCESS_ENVIRONMENT);
71+
java.lang.reflect.Field environmentField = environmentClass.getDeclaredField(ENVIRONMENT);
72+
assertNotNull(environmentField);
73+
environmentField.setAccessible(true);
74+
75+
Object unmodifiableEnvironmentMap = environmentField.get(STATIC_METHOD);
76+
assertNotNull(unmodifiableEnvironmentMap);
77+
assertTrue(UMODIFIABLE_MAP_CLASS.isAssignableFrom(unmodifiableEnvironmentMap.getClass()));
78+
79+
java.lang.reflect.Field underlyingMapField =
80+
unmodifiableEnvironmentMap.getClass().getDeclaredField(SOURCE_MAP);
81+
underlyingMapField.setAccessible(true);
82+
Object underlyingMap = underlyingMapField.get(unmodifiableEnvironmentMap);
83+
assertNotNull(underlyingMap);
84+
assertTrue(MAP_CLASS.isAssignableFrom(underlyingMap.getClass()));
85+
86+
return (Map<String, String>) underlyingMap;
87+
}
88+
89+
@BeforeClass
90+
public static void setEnvVars() throws Exception {
91+
// Java versions 8 and lower start with 1. (1.8, 1.7 etc.).
92+
// Higher versions start with the major version number.
93+
// So this effectively verifies that the test is running on Java 8.
94+
assumeTrue(System.getProperty("java.version", "undefined").startsWith("1."));
95+
assumeFalse(System.getProperty("os.name", "").toLowerCase().startsWith("windows"));
96+
97+
if (Strings.isNullOrEmpty(System.getenv("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW"))) {
98+
Map<String, String> env = getModifiableEnvironment();
99+
env.put("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW", "true");
100+
setEnvVar = true;
101+
}
102+
}
103+
104+
@AfterClass
105+
public static void clearEnvVars() throws Exception {
106+
if (setEnvVar) {
107+
Map<String, String> env = getModifiableEnvironment();
108+
env.remove("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW");
109+
}
110+
}
111+
112+
@Before
113+
public void setupResults() {
114+
query = "select * from my_table";
115+
dml = "insert into my_table (id, value) values (1, 'One')";
116+
String DML_THEN_RETURN_ID = dml + "\nTHEN RETURN `id`";
117+
dmlReturning = "insert into my_table (id, value) values (1, 'One') THEN RETURN *";
118+
119+
super.setupResults();
120+
121+
com.google.spanner.v1.ResultSet resultSet =
122+
com.google.spanner.v1.ResultSet.newBuilder()
123+
.setMetadata(
124+
ResultSetMetadata.newBuilder()
125+
.setRowType(
126+
StructType.newBuilder()
127+
.addFields(
128+
Field.newBuilder()
129+
.setType(Type.newBuilder().setCode(TypeCode.INT64).build())
130+
.setName("id")
131+
.build())
132+
.addFields(
133+
Field.newBuilder()
134+
.setType(Type.newBuilder().setCode(TypeCode.STRING).build())
135+
.setName("value")
136+
.build())
137+
.build())
138+
.build())
139+
.addRows(
140+
ListValue.newBuilder()
141+
.addValues(Value.newBuilder().setStringValue("1").build())
142+
.addValues(Value.newBuilder().setStringValue("One").build())
143+
.build())
144+
.build();
145+
com.google.spanner.v1.ResultSet returnIdResultSet =
146+
com.google.spanner.v1.ResultSet.newBuilder()
147+
.setMetadata(
148+
ResultSetMetadata.newBuilder()
149+
.setRowType(
150+
StructType.newBuilder()
151+
.addFields(
152+
Field.newBuilder()
153+
.setType(Type.newBuilder().setCode(TypeCode.INT64).build())
154+
.setName("id")
155+
.build())
156+
.build())
157+
.build())
158+
.addRows(
159+
ListValue.newBuilder()
160+
.addValues(Value.newBuilder().setStringValue("1").build())
161+
.build())
162+
.build();
163+
mockSpanner.putStatementResult(
164+
StatementResult.query(com.google.cloud.spanner.Statement.of(query), resultSet));
165+
mockSpanner.putStatementResult(
166+
StatementResult.update(com.google.cloud.spanner.Statement.of(dml), 1L));
167+
mockSpanner.putStatementResult(
168+
StatementResult.query(
169+
com.google.cloud.spanner.Statement.of(dmlReturning),
170+
resultSet
171+
.toBuilder()
172+
.setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build())
173+
.build()));
174+
mockSpanner.putStatementResult(
175+
StatementResult.query(
176+
com.google.cloud.spanner.Statement.of(DML_THEN_RETURN_ID),
177+
returnIdResultSet
178+
.toBuilder()
179+
.setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build())
180+
.build()));
181+
}
182+
183+
private String createUrl() {
184+
return String.format(
185+
"jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true",
186+
getPort(), "proj", "inst", "db");
187+
}
188+
189+
@Override
190+
protected Connection createJdbcConnection() throws SQLException {
191+
return DriverManager.getConnection(createUrl());
192+
}
193+
194+
@Test
195+
public void testStatementExecuteQuery() throws SQLException {
196+
try (Connection connection = createJdbcConnection();
197+
Statement statement = connection.createStatement()) {
198+
try (ResultSet resultSet = statement.executeQuery(query)) {
199+
//noinspection StatementWithEmptyBody
200+
while (resultSet.next()) {}
201+
}
202+
}
203+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
204+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
205+
assertTrue(MockServerHelper.getSession(mockSpanner, request.getSession()).getMultiplexed());
206+
}
207+
208+
@Test
209+
public void testStatementExecuteUpdate() throws SQLException {
210+
try (Connection connection = createJdbcConnection();
211+
Statement statement = connection.createStatement()) {
212+
assertEquals(1, statement.executeUpdate(dml));
213+
}
214+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
215+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
216+
assertTrue(MockServerHelper.getSession(mockSpanner, request.getSession()).getMultiplexed());
217+
assertTrue(request.hasTransaction());
218+
assertTrue(request.getTransaction().hasBegin());
219+
assertTrue(request.getTransaction().getBegin().hasReadWrite());
220+
}
221+
222+
@Test
223+
public void testStatementExecuteQueryDmlReturning() throws SQLException {
224+
try (Connection connection = createJdbcConnection();
225+
Statement statement = connection.createStatement()) {
226+
try (ResultSet resultSet = statement.executeQuery(dmlReturning)) {
227+
//noinspection StatementWithEmptyBody
228+
while (resultSet.next()) {}
229+
}
230+
}
231+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
232+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
233+
assertTrue(MockServerHelper.getSession(mockSpanner, request.getSession()).getMultiplexed());
234+
assertTrue(request.hasTransaction());
235+
assertTrue(request.getTransaction().hasBegin());
236+
assertTrue(request.getTransaction().getBegin().hasReadWrite());
237+
}
238+
}

0 commit comments

Comments
 (0)