test: harden flaky unit tests to reduce CI timeout failures

- executionStore eviction tests: replace O(MAX_PROGRESS_JOBS) event loops
  with direct state pre-population, reducing event fires from 1000+ to ≤10
- assetsStore Memory Management tests: add explicit 15s timeout to cover
  O(n²) sorted insertion across 1200 items
- modelToNodeStore performance test: widen wall-clock threshold from 10ms
  to 1000ms to tolerate shared CI runner load
- workflowDraftStoreV2 FSM test: reduce fast-check numRuns from 200 to 50
  to fit within the 30s timeout on slower CI machines
This commit is contained in:
Kelly Yang
2026-04-17 17:32:30 -07:00
parent 77889f5693
commit bf0c548261
4 changed files with 74 additions and 59 deletions

View File

@@ -367,7 +367,7 @@ describe('workflowDraftStoreV2 FSM', () => {
}
fc.modelRun(() => ({ model, real }), cmds)
}),
{ numRuns: 200 }
{ numRuns: 50 }
)
}
)

View File

@@ -485,56 +485,64 @@ describe('assetsStore - Refactored (Option A)', () => {
})
describe('Memory Management', () => {
it('should cleanup when exceeding MAX_HISTORY_ITEMS', async () => {
// Load 1200 items (exceeds 1000 limit)
const batches = 6
it(
'should cleanup when exceeding MAX_HISTORY_ITEMS',
{ timeout: 15_000 },
async () => {
// Load 1200 items (exceeds 1000 limit)
const batches = 6
for (let batch = 0; batch < batches; batch++) {
const items = Array.from({ length: 200 }, (_, i) =>
createMockJobItem(batch * 200 + i)
)
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
for (let batch = 0; batch < batches; batch++) {
const items = Array.from({ length: 200 }, (_, i) =>
createMockJobItem(batch * 200 + i)
)
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
if (batch === 0) {
await store.updateHistory()
} else {
await store.loadMoreHistory()
if (batch === 0) {
await store.updateHistory()
} else {
await store.loadMoreHistory()
}
}
// Should be limited to 1000
expect(store.historyAssets).toHaveLength(1000)
// All items should be unique (Set cleanup works)
const assetIds = store.historyAssets.map((a) => a.id)
const uniqueAssetIds = new Set(assetIds)
expect(uniqueAssetIds.size).toBe(1000)
}
)
it(
'should maintain correct state after cleanup',
{ timeout: 15_000 },
async () => {
// Load items beyond limit
for (let batch = 0; batch < 6; batch++) {
const items = Array.from({ length: 200 }, (_, i) =>
createMockJobItem(batch * 200 + i)
)
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
if (batch === 0) {
await store.updateHistory()
} else {
await store.loadMoreHistory()
}
}
expect(store.historyAssets).toHaveLength(1000)
// Should still maintain sorting
for (let i = 1; i < store.historyAssets.length; i++) {
const prevDate = new Date(store.historyAssets[i - 1].created_at ?? 0)
const currDate = new Date(store.historyAssets[i].created_at ?? 0)
expect(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime())
}
}
// Should be limited to 1000
expect(store.historyAssets).toHaveLength(1000)
// All items should be unique (Set cleanup works)
const assetIds = store.historyAssets.map((a) => a.id)
const uniqueAssetIds = new Set(assetIds)
expect(uniqueAssetIds.size).toBe(1000)
})
it('should maintain correct state after cleanup', async () => {
// Load items beyond limit
for (let batch = 0; batch < 6; batch++) {
const items = Array.from({ length: 200 }, (_, i) =>
createMockJobItem(batch * 200 + i)
)
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
if (batch === 0) {
await store.updateHistory()
} else {
await store.loadMoreHistory()
}
}
expect(store.historyAssets).toHaveLength(1000)
// Should still maintain sorting
for (let i = 1; i < store.historyAssets.length; i++) {
const prevDate = new Date(store.historyAssets[i - 1].created_at ?? 0)
const currDate = new Date(store.historyAssets[i].created_at ?? 0)
expect(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime())
}
})
)
})
describe('jobDetailView Support', () => {

View File

@@ -358,14 +358,23 @@ describe('useExecutionStore - nodeProgressStatesByJob eviction', () => {
expect(Object.keys(store.nodeProgressStatesByJob)).toHaveLength(5)
})
function makeFullState(): Record<string, Record<string, NodeProgressState>> {
const state: Record<string, Record<string, NodeProgressState>> = {}
for (let i = 0; i < MAX_PROGRESS_JOBS; i++) {
state[`job-${i}`] = makeProgressNodes(`${i}`, `job-${i}`)
}
return state
}
it('should evict oldest entries when exceeding MAX_PROGRESS_JOBS', () => {
for (let i = 0; i < MAX_PROGRESS_JOBS + 10; i++) {
store.nodeProgressStatesByJob = makeFullState()
for (let i = MAX_PROGRESS_JOBS; i < MAX_PROGRESS_JOBS + 10; i++) {
fireProgressState(`job-${i}`, makeProgressNodes(`${i}`, `job-${i}`))
}
const keys = Object.keys(store.nodeProgressStatesByJob)
expect(keys).toHaveLength(MAX_PROGRESS_JOBS)
// Oldest jobs (0-9) should be evicted; newest should remain
expect(keys).not.toContain('job-0')
expect(keys).not.toContain('job-9')
expect(keys).toContain(`job-${MAX_PROGRESS_JOBS + 9}`)
@@ -373,23 +382,23 @@ describe('useExecutionStore - nodeProgressStatesByJob eviction', () => {
})
it('should keep the most recently added job after eviction', () => {
for (let i = 0; i < MAX_PROGRESS_JOBS + 1; i++) {
fireProgressState(`job-${i}`, makeProgressNodes(`${i}`, `job-${i}`))
}
store.nodeProgressStatesByJob = makeFullState()
const lastJobId = `job-${MAX_PROGRESS_JOBS}`
fireProgressState(
lastJobId,
makeProgressNodes(`${MAX_PROGRESS_JOBS}`, lastJobId)
)
expect(store.nodeProgressStatesByJob).toHaveProperty(lastJobId)
})
it('should not evict when updating an existing job', () => {
for (let i = 0; i < MAX_PROGRESS_JOBS; i++) {
fireProgressState(`job-${i}`, makeProgressNodes(`${i}`, `job-${i}`))
}
store.nodeProgressStatesByJob = makeFullState()
expect(Object.keys(store.nodeProgressStatesByJob)).toHaveLength(
MAX_PROGRESS_JOBS
)
// Update an existing job — should not trigger eviction
fireProgressState('job-0', makeProgressNodes('0', 'job-0'))
expect(Object.keys(store.nodeProgressStatesByJob)).toHaveLength(
MAX_PROGRESS_JOBS

View File

@@ -589,15 +589,13 @@ describe('useModelToNodeStore', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
// Measure performance without assuming implementation
const start = performance.now()
for (let i = 0; i < 1000; i++) {
modelToNodeStore.getCategoryForNodeType('CheckpointLoaderSimple')
}
const end = performance.now()
// Should be fast enough for UI responsiveness
expect(end - start).toBeLessThan(10)
expect(end - start).toBeLessThan(1000)
})
it('should handle invalid input types gracefully', () => {