Skip to content

Commit 0a9c3d6

Browse files
glasstigerclaude
andcommitted
De-reflect cursor tests; de-flake poison timing
Two test-quality fixes in the send-loop cursor suite; no production behavior change. De-reflection. CatchUpAlignmentTest, PoisonFrameTest, TornDictGuardTest and MirrorLeakTest reached into CursorWebSocketSendLoop by reflection -- reading private constants and fields, invoking private methods, and writing private state -- so a rename broke them as a runtime ReflectiveOperationException instead of a compile error. Add @testonly accessors and thin ...ForTest wrappers on CursorWebSocketSendLoop, each delegating verbatim to the same private member, and make forceMirrorSeedFailureForTest public; then rewire the four tests to call them. The rewrites drive the identical production paths, and the mirror-overflow guard now catches the exact public LineSenderException type rather than unwrapping an InvocationTargetException. De-flake. PoisonFrameTest asserted absolute wall-clock bounds -- a recycle is "immediate" under 300 ms, and a recycle count fits a fixed sleep window -- that a CI GC pause or starved scheduler can trip. Replace them with predicates only a real regression fails: consecutive recycle gaps must clear 3/4 of the reconnect backoff (an unpaced loop's sub-millisecond gaps fail, while a slow test thread only yields more paced samples); the deterministic zeroProgressRecycles branch value (level 0, then 1, then reset on progress); a first*2 <= second relative bound; and the kept second >= backoff lower bound, which starvation can only lengthen. Split the dwell test into a huge-window case (threshold reached, window still open, so no escalation -- asserting poisonStrikes == 3) and a small-window case crossed by a sleep strictly longer than the window, which must escalate to the PROTOCOL_VIOLATION terminal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 54e21f8 commit 0a9c3d6

5 files changed

Lines changed: 384 additions & 360 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/CursorWebSocketSendLoop.java

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public final class CursorWebSocketSendLoop implements QuietCloseable {
206206
// deleted. Production never sets it; volatile only so a test thread's write is
207207
// visible to the loop under test.
208208
@TestOnly
209-
static volatile boolean forceMirrorSeedFailureForTest;
209+
public static volatile boolean forceMirrorSeedFailureForTest;
210210
// Pre-converted to nanos for the comparison in sendDurableAckKeepaliveIfDue.
211211
// Zero or negative disables the keepalive entirely.
212212
private final long durableAckKeepaliveIntervalNanos;
@@ -1091,6 +1091,16 @@ public static boolean isTerminalCloseCode(int code) {
10911091
}
10921092
}
10931093

1094+
@TestOnly
1095+
public static int maxCatchUpCapGapAttempts() {
1096+
return MAX_CATCHUP_CAP_GAP_ATTEMPTS;
1097+
}
1098+
1099+
@TestOnly
1100+
public static int maxSentDictBytes() {
1101+
return MAX_SENT_DICT_BYTES;
1102+
}
1103+
10941104
/**
10951105
* Surfaces any error the I/O thread recorded. Called by the producer
10961106
* thread (typically from inside its append wrapper) so failures don't
@@ -2895,6 +2905,140 @@ public int catchUpFrameGrowthCount() {
28952905
return catchUpFrameGrowthCount;
28962906
}
28972907

2908+
@TestOnly
2909+
public int catchUpCapGapAttempts() {
2910+
return catchUpCapGapAttempts;
2911+
}
2912+
2913+
@TestOnly
2914+
public long catchUpCapGapFirstNanos() {
2915+
return catchUpCapGapFirstNanos;
2916+
}
2917+
2918+
@TestOnly
2919+
public long catchUpCapGapMinEscalationWindowNanos() {
2920+
return catchUpCapGapMinEscalationWindowNanos;
2921+
}
2922+
2923+
@TestOnly
2924+
public long fsnAtZero() {
2925+
return fsnAtZero;
2926+
}
2927+
2928+
@TestOnly
2929+
public long poisonFsn() {
2930+
return poisonFsn;
2931+
}
2932+
2933+
@TestOnly
2934+
public int poisonStrikes() {
2935+
return poisonStrikes;
2936+
}
2937+
2938+
@TestOnly
2939+
public long sentDictBytesAddr() {
2940+
return sentDictBytesAddr;
2941+
}
2942+
2943+
@TestOnly
2944+
public int sentDictBytesLen() {
2945+
return sentDictBytesLen;
2946+
}
2947+
2948+
@TestOnly
2949+
public boolean sentDictBytesOwned() {
2950+
return sentDictBytesOwned;
2951+
}
2952+
2953+
@TestOnly
2954+
public int sentDictCount() {
2955+
return sentDictCount;
2956+
}
2957+
2958+
@TestOnly
2959+
public int zeroProgressRecycles() {
2960+
return zeroProgressRecycles;
2961+
}
2962+
2963+
@TestOnly
2964+
public void accumulateSentDictForTest(long payloadAddr, int payloadLen, int deltaStart) {
2965+
accumulateSentDict(payloadAddr, payloadLen, deltaStart);
2966+
}
2967+
2968+
@TestOnly
2969+
public void connectLoopForTest(Throwable initial, String phase, long paceFirstAttemptMillis) {
2970+
connectLoop(initial, phase, paceFirstAttemptMillis);
2971+
}
2972+
2973+
@TestOnly
2974+
public void deliverCloseForTest(int code, String reason) {
2975+
responseHandler.onClose(code, reason);
2976+
}
2977+
2978+
@TestOnly
2979+
public void deliverResponseForTest(long payloadPtr, int payloadLen) {
2980+
responseHandler.onBinaryMessage(payloadPtr, payloadLen);
2981+
}
2982+
2983+
@TestOnly
2984+
public void ensureSentDictCapacityForTest(long required) {
2985+
ensureSentDictCapacity(required);
2986+
}
2987+
2988+
@TestOnly
2989+
public void positionCursorForStartForTest() {
2990+
positionCursorForStart();
2991+
}
2992+
2993+
@TestOnly
2994+
public void seedSentDictMirrorForTest(long addr, int bytes, int symbolCount) {
2995+
sentDictBytesAddr = addr;
2996+
sentDictBytesCapacity = bytes;
2997+
sentDictBytesLen = bytes;
2998+
sentDictCount = symbolCount;
2999+
sentDictBytesOwned = true;
3000+
}
3001+
3002+
@TestOnly
3003+
public void sendCatchUpChunkForTest(int deltaStart, int deltaCount, long symbolsAddr, int symbolsLen) {
3004+
sendCatchUpChunk(deltaStart, deltaCount, symbolsAddr, symbolsLen);
3005+
}
3006+
3007+
@TestOnly
3008+
public void setCatchUpCapGapFirstNanosForTest(long nanos) {
3009+
catchUpCapGapFirstNanos = nanos;
3010+
}
3011+
3012+
@TestOnly
3013+
public void setDataFrameSentThisConnectionForTest(boolean value) {
3014+
dataFrameSentThisConnection = value;
3015+
}
3016+
3017+
@TestOnly
3018+
public void setFsnAtZeroForTest(long value) {
3019+
fsnAtZero = value;
3020+
}
3021+
3022+
@TestOnly
3023+
public void setNextWireSeqForTest(long value) {
3024+
nextWireSeq = value;
3025+
}
3026+
3027+
@TestOnly
3028+
public void setRunningForTest(boolean value) {
3029+
running = value;
3030+
}
3031+
3032+
@TestOnly
3033+
public void setWireBaselineWithCatchUpForTest(long replayStart) {
3034+
setWireBaselineWithCatchUp(replayStart);
3035+
}
3036+
3037+
@TestOnly
3038+
public boolean trySendOneForTest() {
3039+
return trySendOne();
3040+
}
3041+
28983042
private void ensureCatchUpFrameCapacity(int required) {
28993043
if (catchUpFrameCapacity >= required) {
29003044
return;

0 commit comments

Comments
 (0)