3232import org .junit .Assert ;
3333import org .junit .Test ;
3434
35- import java .lang .reflect .Constructor ;
3635import java .lang .reflect .Field ;
37- import java .lang .reflect .InvocationTargetException ;
38- import java .lang .reflect .Method ;
3936import java .util .ArrayList ;
4037import java .util .Arrays ;
4138import java .util .List ;
6158 * {@code CursorWebSocketSendLoopJvmErrorTest}: {@code Unsafe.allocateInstance}
6259 * plus reflective wiring of the fields the connect walk dereferences, with the
6360 * {@code setClientFactoryOverride} test seam substituting a stub client whose
64- * {@code connect()} throws.
61+ * {@code connect()} throws. The walk itself is driven without reflection:
62+ * the public {@code newReconnectFactory().reconnect()} is a one-line
63+ * delegation to the private {@code buildAndConnect}.
6564 */
6665public class QwpWebSocketSenderJvmErrorCleanupTest {
6766
@@ -85,8 +84,8 @@ public void testErrorDuringConnectClosesClientAndStopsWalk() throws Exception {
8584 try {
8685 invokeBuildAndConnect (sender );
8786 Assert .fail ("a JVM Error must propagate out of buildAndConnect" );
88- } catch (InvocationTargetException ite ) {
89- Assert .assertSame ("the original Error must surface" , oom , ite . getCause () );
87+ } catch (OutOfMemoryError e ) {
88+ Assert .assertSame ("the original Error must surface" , oom , e );
9089 }
9190 Assert .assertEquals ("Error must stop the walk on the first attempt" , 1 , built .size ());
9291 Assert .assertEquals ("half-built client must be closed exactly once" ,
@@ -112,9 +111,9 @@ public void testCloseFailureDoesNotMaskOriginalError() throws Exception {
112111 try {
113112 invokeBuildAndConnect (sender );
114113 Assert .fail ("a JVM Error must propagate out of buildAndConnect" );
115- } catch (InvocationTargetException ite ) {
114+ } catch (OutOfMemoryError e ) {
116115 Assert .assertSame ("close() failure must not mask the original Error" ,
117- oom , ite . getCause () );
116+ oom , e );
118117 }
119118 Assert .assertEquals ("close must have been attempted" , 1 , stub .closeCalls );
120119 }
@@ -141,8 +140,8 @@ public void testErrorInSuccessTailClosesConnectedClient() throws Exception {
141140 try {
142141 invokeBuildAndConnect (sender );
143142 Assert .fail ("a JVM Error must propagate out of buildAndConnect" );
144- } catch (InvocationTargetException ite ) {
145- Assert .assertSame ("the original Error must surface" , oom , ite . getCause () );
143+ } catch (OutOfMemoryError e ) {
144+ Assert .assertSame ("the original Error must surface" , oom , e );
146145 }
147146 Assert .assertEquals ("Error must stop the walk on the first attempt" , 1 , built .size ());
148147 Assert .assertEquals ("connected client must be closed exactly once" ,
@@ -169,9 +168,9 @@ public void testSuccessTailCloseFailureDoesNotMaskOriginalError() throws Excepti
169168 try {
170169 invokeBuildAndConnect (sender );
171170 Assert .fail ("a JVM Error must propagate out of buildAndConnect" );
172- } catch (InvocationTargetException ite ) {
171+ } catch (OutOfMemoryError e ) {
173172 Assert .assertSame ("close() failure must not mask the original Error" ,
174- oom , ite . getCause () );
173+ oom , e );
175174 }
176175 Assert .assertEquals ("close must have been attempted" , 1 , stub .closeCalls );
177176 }
@@ -194,8 +193,8 @@ public void testErrorAtSuccessTailEntryClosesConnectedClient() throws Exception
194193 try {
195194 invokeBuildAndConnect (sender );
196195 Assert .fail ("a JVM Error must propagate out of buildAndConnect" );
197- } catch (InvocationTargetException ite ) {
198- Assert .assertSame ("the original Error must surface" , oom , ite . getCause () );
196+ } catch (OutOfMemoryError e ) {
197+ Assert .assertSame ("the original Error must surface" , oom , e );
199198 }
200199 Assert .assertEquals ("connected client must be closed exactly once" ,
201200 1 , stub .closeCalls );
@@ -222,9 +221,8 @@ public void testExceptionPathStillClosesAndWalksAllEndpoints() throws Exception
222221 try {
223222 invokeBuildAndConnect (sender );
224223 Assert .fail ("an exhausted round must surface LineSenderException" );
225- } catch (InvocationTargetException ite ) {
226- Assert .assertTrue ("expected LineSenderException, got " + ite .getCause (),
227- ite .getCause () instanceof LineSenderException );
224+ } catch (LineSenderException expected ) {
225+ // expected: the walk exhausted the round and surfaced the failure
228226 }
229227 Assert .assertEquals ("an Exception must keep the walk going" , 2 , built .size ());
230228 for (StubClient c : built ) {
@@ -269,20 +267,16 @@ private static void installFactory(QwpWebSocketSender sender,
269267 }
270268
271269 /**
272- * Drives the private connect walk through its private foreground
273- * {@code ReconnectSupplier} (no-arg: abortCheck null means foreground;
274- * the bare sender's null {@code cursorSendLoop} and false {@code closed}
275- * make {@code isAborted()} false).
270+ * Drives the private connect walk through its foreground
271+ * {@code ReconnectSupplier}, obtained via the public production accessor
272+ * {@link QwpWebSocketSender#newReconnectFactory()} (no abort gate: the
273+ * bare sender's null {@code cursorSendLoop} and false {@code closed}
274+ * make {@code isAborted()} false). {@code ReconnectFactory.reconnect()}
275+ * is a one-line delegation to {@code buildAndConnect(this)}, so failures
276+ * surface unwrapped.
276277 */
277278 private static void invokeBuildAndConnect (QwpWebSocketSender sender ) throws Exception {
278- Class <?> supplierClass = Class .forName (
279- "io.questdb.client.cutlass.qwp.client.QwpWebSocketSender$ReconnectSupplier" );
280- Constructor <?> ctor = supplierClass .getDeclaredConstructor (QwpWebSocketSender .class );
281- ctor .setAccessible (true );
282- Object ctx = ctor .newInstance (sender );
283- Method m = QwpWebSocketSender .class .getDeclaredMethod ("buildAndConnect" , supplierClass );
284- m .setAccessible (true );
285- m .invoke (sender , ctx );
279+ sender .newReconnectFactory ().reconnect ();
286280 }
287281
288282 private static StubClient newStubClient () {
0 commit comments