Skip to main content

condition-based-waiting

Replace arbitrary timeouts with condition polling for reliable async tests

来源信息

仓库
obra/clank
最近来源活动
2025年10月6日 01:12
检测到的 SKILL.md 语言
英语
星标
41
分支
27

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
2 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
Condition-Based Waiting
description
Replace arbitrary timeouts with condition polling for reliable async tests
when_to_use
When tests use setTimeout/sleep and are flaky or timing-dependent
version
1.0.0
languages
all
# Condition-Based Waiting ## Overview Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI. **Core principle:** Wait for the actual condition you care about, not a guess about how long it takes. ## When to Use ```dot digraph when_to_use { "Test uses setTimeout/sleep?" [shape=diamond]; "Testing timing behavior?" [shape=diamond]; "Document WHY timeout needed" [shape=box]; "Use condition-based waiting" [shape=box]; "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"]; "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"]; "Testing timing behavior?" -> "Use condition-based waiting" [label="no"]; } ``` **Use when:** - Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`) - Tests are flaky (pass sometimes, fail under load) - Tests timeout when run in parallel - Waiting for async operations to complete **Don't use when:** - Testing actual timing behavior (debounce, throttle intervals) - Always document WHY if using arbitrary timeout ## Core Pattern ```typescript // ❌ BEFORE: Guessing at timing await new Promise(r => setTimeout(r, 50)); const result = getResult(); expect(result).toBeDefined(); // ✅ AFTER: Waiting for condition await waitFor(() => getResult() !== undefined); const result = getResult(); expect(result).toBeDefined(); ``` ## Quick Patterns | Scenario | Pattern | |----------|---------| | Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` | | Wait for state | `waitFor(() => machine.state === 'ready')` | | Wait for count | `waitFor(() => items.length >= 5)` | | Wait for file | `waitFor(() => fs.existsSync(path))` | | Complex condition | `waitFor(() => obj.ready && obj.value > 10)` | ## Implementation Generic polling function: ```typescript async function waitFor<T>( condition: () => T | undefined | null | false, description: string, timeoutMs = 5000 ): Promise<T> { const startTime = Date.now(); while (true) { const result = condition(); if (result) return result; if (Date.now() - startTime > timeoutMs) { throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`); } await new Promise(r => setTimeout(r, 10)); // Poll every 10ms } } ``` See @example.ts for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session. ## Common Mistakes **❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU **✅ Fix:** Poll every 10ms **❌ No timeout:** Loop forever if condition never met **✅ Fix:** Always include timeout with clear error **❌ Stale data:** Cache state before loop **✅ Fix:** Call getter inside loop for fresh data ## When Arbitrary Timeout IS Correct ```typescript // Tool ticks every 100ms - need 2 ticks to verify partial output await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior // 200ms = 2 ticks at 100ms intervals - documented and justified ``` **Requirements:** 1. First wait for triggering condition 2. Based on known timing (not guessing) 3. Comment explaining WHY ## Real-World Impact From debugging session (2025-10-03): - Fixed 15 flaky tests across 3 files - Pass rate: 60% → 100% - Execution time: 40% faster - No more race conditions
在 GitHub 查看