11import { describe , expect , test , vi } from "vitest" ;
22import { describeDriverMatrix } from "./shared-matrix" ;
3- import { setupDriverTest } from "./shared-utils" ;
3+ import { setupDriverTest , waitFor } from "./shared-utils" ;
44
55const STRESS_TEST_TIMEOUT_MS = 60_000 ;
6+ const KITCHEN_SINK_TEST_TIMEOUT_MS = 120_000 ;
67const ACTOR_READY_TIMEOUT_MS = 15_000 ;
8+ const RUNTIME_LOG_TAIL_CHARS = 20_000 ;
9+
10+ async function withRuntimeLogTail < T > (
11+ getRuntimeOutput : ( ) => string ,
12+ fn : ( ) => Promise < T > ,
13+ ) : Promise < T > {
14+ try {
15+ return await fn ( ) ;
16+ } catch ( error ) {
17+ const runtimeOutput = getRuntimeOutput ( ) ;
18+ const runtimeTail = runtimeOutput . slice ( - RUNTIME_LOG_TAIL_CHARS ) ;
19+ if ( error instanceof Error && runtimeTail ) {
20+ error . message = `${ error . message } \n\nRuntime log tail:\n${ runtimeTail } ` ;
21+ }
22+ throw error ;
23+ }
24+ }
725
826/**
927 * Stress and resilience tests for the SQLite database subsystem.
@@ -64,7 +82,10 @@ describeDriverMatrix("Actor Db Stress", (driverTestConfig) => {
6482 test (
6583 "rapid create-insert-destroy cycles handle DB lifecycle correctly" ,
6684 async ( c ) => {
67- const { client } = await setupDriverTest ( c , driverTestConfig ) ;
85+ const { client, getRuntimeOutput } = await setupDriverTest (
86+ c ,
87+ driverTestConfig ,
88+ ) ;
6889
6990 // Perform rapid cycles of create -> insert -> destroy.
7091 // This exercises the close_database path racing with
@@ -74,14 +95,34 @@ describeDriverMatrix("Actor Db Stress", (driverTestConfig) => {
7495 `stress-cycle-${ i } -${ crypto . randomUUID ( ) } ` ,
7596 ] ;
7697 const getActor = ( ) => client . dbStressActor . getOrCreate ( actorKey ) ;
77- const actor = getActor ( ) ;
78- await actor . ready ;
79- await actor . insertBatch ( 10 ) ;
8098
81- const verifyActor = getActor ( ) ;
82- await verifyActor . ready ;
83- const count = await verifyActor . getCount ( ) ;
84- expect ( count ) . toBeGreaterThanOrEqual ( 10 ) ;
99+ // Poll the first insert because the actor can still be starting when the initial DB action is sent.
100+ await vi . waitFor (
101+ async ( ) => {
102+ await withRuntimeLogTail (
103+ getRuntimeOutput ,
104+ ( ) => getActor ( ) . insertBatch ( 10 ) ,
105+ ) ;
106+ } ,
107+ { timeout : ACTOR_READY_TIMEOUT_MS , interval : 100 } ,
108+ ) ;
109+
110+ // Reacquire the keyed handle before verifying the write.
111+ // The direct target from the insert can already be moving
112+ // through sleep teardown under the task model.
113+ await vi . waitFor (
114+ async ( ) => {
115+ const count = await withRuntimeLogTail (
116+ getRuntimeOutput ,
117+ ( ) =>
118+ client . dbStressActor
119+ . getOrCreate ( actorKey )
120+ . getCount ( ) ,
121+ ) ;
122+ expect ( count ) . toBeGreaterThanOrEqual ( 10 ) ;
123+ } ,
124+ { timeout : ACTOR_READY_TIMEOUT_MS , interval : 100 } ,
125+ ) ;
85126
86127 // Destroy the actor (triggers close_database).
87128 await getActor ( ) . destroy ( ) ;
@@ -93,7 +134,10 @@ describeDriverMatrix("Actor Db Stress", (driverTestConfig) => {
93134 test (
94135 "DB operations complete without excessive blocking" ,
95136 async ( c ) => {
96- const { client } = await setupDriverTest ( c , driverTestConfig ) ;
137+ const { client, getRuntimeOutput } = await setupDriverTest (
138+ c ,
139+ driverTestConfig ,
140+ ) ;
97141
98142 const actorKey = [ `stress-health-${ crypto . randomUUID ( ) } ` ] ;
99143
@@ -104,9 +148,11 @@ describeDriverMatrix("Actor Db Stress", (driverTestConfig) => {
104148 // expected because the action itself runs on that loop.
105149 const health = await vi . waitFor (
106150 async ( ) =>
107- client . dbStressActor
108- . getOrCreate ( actorKey )
109- . measureEventLoopHealth ( 100 ) ,
151+ withRuntimeLogTail ( getRuntimeOutput , ( ) =>
152+ client . dbStressActor
153+ . getOrCreate ( actorKey )
154+ . measureEventLoopHealth ( 100 ) ,
155+ ) ,
110156 { timeout : ACTOR_READY_TIMEOUT_MS , interval : 100 } ,
111157 ) ;
112158
@@ -119,14 +165,101 @@ describeDriverMatrix("Actor Db Stress", (driverTestConfig) => {
119165 // Poll the integrity check because the actor may still be finishing the prior async insert loop.
120166 const integrity = await vi . waitFor (
121167 async ( ) =>
122- client . dbStressActor
123- . getOrCreate ( actorKey )
124- . integrityCheck ( ) ,
168+ withRuntimeLogTail ( getRuntimeOutput , ( ) =>
169+ client . dbStressActor
170+ . getOrCreate ( actorKey )
171+ . integrityCheck ( ) ,
172+ ) ,
125173 { timeout : ACTOR_READY_TIMEOUT_MS , interval : 100 } ,
126174 ) ;
127175 expect ( integrity . toLowerCase ( ) ) . toBe ( "ok" ) ;
128176 } ,
129177 STRESS_TEST_TIMEOUT_MS ,
130178 ) ;
179+
180+ test (
181+ "repeated autocommit upserts keep sqlite head txid consistent" ,
182+ async ( c ) => {
183+ const { client, getRuntimeOutput } = await setupDriverTest (
184+ c ,
185+ driverTestConfig ,
186+ ) ;
187+ const actor = client . dbStressActor . getOrCreate ( [
188+ `stress-autocommit-upsert-${ crypto . randomUUID ( ) } ` ,
189+ ] ) ;
190+
191+ await actor . reset ( ) ;
192+
193+ const count = await withRuntimeLogTail (
194+ getRuntimeOutput ,
195+ ( ) => actor . upsertMetaRows ( 240 ) ,
196+ ) ;
197+ expect ( count ) . toBe ( 32 ) ;
198+
199+ const integrity = await withRuntimeLogTail (
200+ getRuntimeOutput ,
201+ ( ) => actor . integrityCheck ( ) ,
202+ ) ;
203+ expect ( integrity . toLowerCase ( ) ) . toBe ( "ok" ) ;
204+ } ,
205+ STRESS_TEST_TIMEOUT_MS ,
206+ ) ;
207+
208+ test (
209+ "kitchen sink sqlite smoke survives write churn and wake" ,
210+ async ( c ) => {
211+ const { client, getRuntimeOutput } = await setupDriverTest (
212+ c ,
213+ driverTestConfig ,
214+ ) ;
215+ const actor = client . dbStressActor . getOrCreate ( [
216+ `stress-kitchen-sink-${ crypto . randomUUID ( ) } ` ,
217+ ] ) ;
218+
219+ await actor . reset ( ) ;
220+
221+ const first = await withRuntimeLogTail (
222+ getRuntimeOutput ,
223+ ( ) => actor . kitchenSinkSmoke ( 320 ) ,
224+ ) ;
225+ expect ( first . metaCount ) . toBeGreaterThanOrEqual ( 19 ) ;
226+ expect ( first . dataCount ) . toBeGreaterThan ( 0 ) ;
227+ expect ( first . payloadCount ) . toBeGreaterThan ( 0 ) ;
228+ expect ( first . pageCount ) . toBeGreaterThan ( 0 ) ;
229+ expect ( first . integrity . toLowerCase ( ) ) . toBe ( "ok" ) ;
230+
231+ const burst = await withRuntimeLogTail ( getRuntimeOutput , ( ) =>
232+ Promise . all ( [
233+ actor . upsertMetaRows ( 320 ) ,
234+ actor . kitchenSinkSmoke ( 96 ) ,
235+ actor . upsertMetaRows ( 320 ) ,
236+ ] ) ,
237+ ) ;
238+ expect ( burst [ 0 ] ) . toBeGreaterThanOrEqual ( 32 ) ;
239+ expect ( burst [ 1 ] . integrity . toLowerCase ( ) ) . toBe ( "ok" ) ;
240+ expect ( burst [ 2 ] ) . toBeGreaterThanOrEqual ( 32 ) ;
241+
242+ await actor . triggerSleep ( ) ;
243+ await waitFor ( driverTestConfig , 250 ) ;
244+
245+ // Poll because the actor can still be in the stopping window after triggerSleep.
246+ const afterWake = await vi . waitFor (
247+ async ( ) =>
248+ await withRuntimeLogTail (
249+ getRuntimeOutput ,
250+ ( ) => actor . kitchenSinkSmoke ( 96 ) ,
251+ ) ,
252+ { timeout : ACTOR_READY_TIMEOUT_MS , interval : 100 } ,
253+ ) ;
254+ expect ( afterWake . metaCount ) . toBeGreaterThanOrEqual (
255+ first . metaCount ,
256+ ) ;
257+ expect ( afterWake . dataCount ) . toBeGreaterThan ( first . dataCount ) ;
258+ expect ( afterWake . payloadCount ) . toBeGreaterThan ( 0 ) ;
259+ expect ( afterWake . pageCount ) . toBeGreaterThan ( 0 ) ;
260+ expect ( afterWake . integrity . toLowerCase ( ) ) . toBe ( "ok" ) ;
261+ } ,
262+ KITCHEN_SINK_TEST_TIMEOUT_MS ,
263+ ) ;
131264 } ) ;
132265} , { encodings : [ "bare" ] } ) ;
0 commit comments