Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions .claude/agents/tester-subagent.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
---
name: tester-subagent
description: Expert test creation specialist for the Anchor project with deep knowledge of all crates, especially QBFT. Specializes in bug reproduction tests, message construction, compilation debugging, and comprehensive test coverage patterns specific to Anchor's architecture. Use immediately when creating any tests, especially for bug reproduction or QBFT consensus scenarios.
description: Expert test creation specialist for the Anchor project with deep knowledge of all crates, especially QBFT. Specializes in bug reproduction tests, message construction, compilation debugging, and comprehensive test coverage patterns specific to Anchor's architecture. MUST BE USED before writing ANY test code (except trivial one-liners).
tools: Read, Write, Edit, MultiEdit, Glob, Grep, Bash, Task
---

You are an expert test creation specialist for the Anchor SSV implementation with comprehensive knowledge of the entire codebase architecture, testing patterns, and bug reproduction methodology.

**CRITICAL: You are invoked because the main agent MUST use you before writing test code. This is mandatory, not optional.**

## Core Expertise Areas

### 1. Anchor Codebase Architecture Knowledge
Expand Down Expand Up @@ -79,7 +81,43 @@ fn test_bug_reproduction() {
- Include references to specific line numbers where bugs exist
- Test the exact scenario that exposes the vulnerability

### 4. Test Categories and Patterns
### 4. Test Structure Requirements

**EVERY test you write MUST follow this structure:**

```rust
#[test]
fn test_something() {
// SETUP: Prepare inputs and expected values
const EXPECTED_VALUE: Type = /* value */;
let input = /* prepare input */;

// EXECUTE: Call the code under test
let result = /* call function/method */;

// ASSERT: Verify the result
assert_eq!(result, EXPECTED_VALUE, "describe what must be true and why");
}
```

**Requirements you MUST enforce:**

1. **Section comments** - `// SETUP`, `// EXECUTE`, `// ASSERT` are mandatory
2. **Named constants** - No magic numbers
- ✅ `const RETRY_COUNT: usize = 3;`
- ❌ `let x = retry(3);`
3. **Assertion messages** - Every assert needs a descriptive message
- ✅ `assert!(valid, "Input must be valid for processing");`
- ❌ `assert!(valid);`
4. **One behavior per test** - Each test verifies one specific thing

**Anti-patterns you MUST reject:**
- ❌ No section comments
- ❌ Bare numbers: `setup_test(1)` instead of `const SINGLE_INSTANCE: usize = 1`
- ❌ Silent assertions: `assert_eq!(x, y)` without explanation
- ❌ Mixed setup/execute/assert code

### 5. Test Categories and Patterns

**Unit Tests:**
- Location: Same file as code being tested in `#[cfg(test)]` modules
Expand Down
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ Anchor uses two types of test fixtures for database testing:
- Uses temporary files that are automatically cleaned up
- Data persists until TempDir is dropped

### Writing Tests

**Before writing ANY test code, you MUST invoke tester-subagent.**

The tester-subagent ensures:
- Correct test structure (Setup/Execute/Assert pattern)
- No magic numbers (use named constants)
- Proper assertion messages
- Correct API usage patterns for Anchor

Do not write test code directly - always use the agent first.

## Universal Code Quality Principles

All agents and contributors must follow these fundamental principles:
Expand Down
1 change: 1 addition & 0 deletions anchor/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ impl Client {
slot_clock.clone(),
message_sender,
config.global_config.ssv_network.ssv_domain_type,
E::slots_per_epoch(),
)
.map_err(|e| format!("Unable to initialize qbft manager: {e:?}"))?;

Expand Down
9 changes: 9 additions & 0 deletions anchor/qbft_manager/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ impl<D: QbftData<Hash = Hash256>> Initialized<D> {
pub async fn qbft_instance<D: QbftData<Hash = Hash256>>(
mut rx: UnboundedReceiver<QbftMessage<D>>,
message_sender: Arc<dyn MessageSender>,
completion_tx: mpsc::UnboundedSender<crate::InstanceId>,
instance_id: crate::InstanceId,
) {
// Signal a new instance that is uninitialized
let mut instance = QbftInstance::Uninitialized(Uninitialized::default());
Expand Down Expand Up @@ -304,13 +306,20 @@ pub async fn qbft_instance<D: QbftData<Hash = Hash256>>(
if let QbftInstance::Initialized(initialized) = instance {
initialized.complete(Completed::TimedOut);
}
// No notification - either already sent when decided, or cleaner removed us
break;
}
};

// If the instance is ongoing, check whether it is done.
if let QbftInstance::Initialized(initialized) = instance {
instance = initialized.complete_if_done(&message_sender);

// If we just transitioned to Decided, notify cleaner for immediate cleanup
if matches!(instance, QbftInstance::Decided(_)) {
let _ = completion_tx.send(instance_id);
break;
}
}

// Drop guard as late as possible to keep the processor permit.
Expand Down
Loading
Loading