mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-01-07 07:43:09 +00:00
156 lines
4.8 KiB
Markdown
156 lines
4.8 KiB
Markdown
|
|
# Testing Philosophy
|
|||
|
|
|
|||
|
|
This framework embodies specific principles that shape how you author and run
|
|||
|
|
scenarios. Understanding these principles helps you write effective tests and
|
|||
|
|
interpret results correctly.
|
|||
|
|
|
|||
|
|
## Declarative over Imperative
|
|||
|
|
|
|||
|
|
Describe **what** you want to test, not **how** to orchestrate it:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Good: declarative
|
|||
|
|
ScenarioBuilder::topology()
|
|||
|
|
.network_star()
|
|||
|
|
.validators(2)
|
|||
|
|
.executors(1)
|
|||
|
|
.apply()
|
|||
|
|
.transactions()
|
|||
|
|
.rate(5) // 5 transactions per block
|
|||
|
|
.apply()
|
|||
|
|
.expect_consensus_liveness()
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
// Bad: imperative (framework doesn't work this way)
|
|||
|
|
// spawn_validator(); spawn_executor();
|
|||
|
|
// loop { submit_tx(); check_block(); }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Why it matters:** The framework handles deployment, readiness, and cleanup.
|
|||
|
|
You focus on test intent, not infrastructure orchestration.
|
|||
|
|
|
|||
|
|
## Protocol Time, Not Wall Time
|
|||
|
|
|
|||
|
|
Reason in **blocks** and **consensus intervals**, not wall-clock seconds.
|
|||
|
|
|
|||
|
|
**Consensus defaults:**
|
|||
|
|
- Slot duration: 2 seconds (NTP-synchronized, configurable via `CONSENSUS_SLOT_TIME`)
|
|||
|
|
- Active slot coefficient: 0.9 (90% block probability per slot)
|
|||
|
|
- Expected rate: ~27 blocks per minute
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Good: protocol-oriented thinking
|
|||
|
|
let plan = ScenarioBuilder::topology()
|
|||
|
|
.network_star()
|
|||
|
|
.validators(2)
|
|||
|
|
.executors(1)
|
|||
|
|
.apply()
|
|||
|
|
.transactions()
|
|||
|
|
.rate(5) // 5 transactions per block
|
|||
|
|
.apply()
|
|||
|
|
.with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks
|
|||
|
|
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
// Bad: wall-clock assumptions
|
|||
|
|
// "I expect exactly 30 blocks in 60 seconds"
|
|||
|
|
// This breaks on slow CI where slot timing might drift
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Why it matters:** Slot timing is fixed (2s by default, NTP-synchronized), so the
|
|||
|
|
expected number of blocks is predictable: ~27 blocks in 60s with the default
|
|||
|
|
0.9 active slot coefficient. The framework calculates expected blocks from slot
|
|||
|
|
duration and run window, making assertions protocol-based rather than tied to
|
|||
|
|
specific wall-clock expectations. Assert on "blocks produced relative to slots"
|
|||
|
|
not "blocks produced in exact wall-clock seconds".
|
|||
|
|
|
|||
|
|
## Determinism First, Chaos When Needed
|
|||
|
|
|
|||
|
|
**Default scenarios are repeatable:**
|
|||
|
|
- Fixed topology
|
|||
|
|
- Predictable traffic rates
|
|||
|
|
- Deterministic checks
|
|||
|
|
|
|||
|
|
**Chaos is opt-in:**
|
|||
|
|
```rust
|
|||
|
|
// Separate: functional test (deterministic)
|
|||
|
|
let plan = ScenarioBuilder::topology()
|
|||
|
|
.network_star()
|
|||
|
|
.validators(2)
|
|||
|
|
.executors(1)
|
|||
|
|
.apply()
|
|||
|
|
.transactions()
|
|||
|
|
.rate(5) // 5 transactions per block
|
|||
|
|
.apply()
|
|||
|
|
.expect_consensus_liveness()
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
// Separate: chaos test (introduces randomness)
|
|||
|
|
let chaos_plan = ScenarioBuilder::topology()
|
|||
|
|
.network_star()
|
|||
|
|
.validators(3)
|
|||
|
|
.executors(2)
|
|||
|
|
.apply()
|
|||
|
|
.enable_node_control()
|
|||
|
|
.chaos()
|
|||
|
|
.restart()
|
|||
|
|
.apply()
|
|||
|
|
.transactions()
|
|||
|
|
.rate(5) // 5 transactions per block
|
|||
|
|
.apply()
|
|||
|
|
.expect_consensus_liveness()
|
|||
|
|
.build();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Why it matters:** Mixing determinism with chaos creates noisy, hard-to-debug
|
|||
|
|
failures. Separate concerns make failures actionable.
|
|||
|
|
|
|||
|
|
## Observable Health Signals
|
|||
|
|
|
|||
|
|
Prefer **user-facing signals** over internal state:
|
|||
|
|
|
|||
|
|
**Good checks:**
|
|||
|
|
- Blocks progressing at expected rate (liveness)
|
|||
|
|
- Transactions included within N blocks (inclusion)
|
|||
|
|
- DA blobs retrievable (availability)
|
|||
|
|
|
|||
|
|
**Avoid internal checks:**
|
|||
|
|
- Memory pool size
|
|||
|
|
- Internal service state
|
|||
|
|
- Cache hit rates
|
|||
|
|
|
|||
|
|
**Why it matters:** User-facing signals reflect actual system health.
|
|||
|
|
Internal state can be "healthy" while the system is broken from a user
|
|||
|
|
perspective.
|
|||
|
|
|
|||
|
|
## Minimum Run Windows
|
|||
|
|
|
|||
|
|
Always run long enough for **meaningful block production**:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Bad: too short
|
|||
|
|
.with_run_duration(Duration::from_secs(5)) // ~2 blocks (with default 2s slots, 0.9 coeff)
|
|||
|
|
|
|||
|
|
// Good: enough blocks for assertions
|
|||
|
|
.with_run_duration(Duration::from_secs(60)) // ~27 blocks (with default 2s slots, 0.9 coeff)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** Block counts assume default consensus parameters:
|
|||
|
|
- Slot duration: 2 seconds (configurable via `CONSENSUS_SLOT_TIME`)
|
|||
|
|
- Active slot coefficient: 0.9 (90% block probability per slot)
|
|||
|
|
- Formula: `blocks ≈ (duration / slot_duration) × active_slot_coeff`
|
|||
|
|
|
|||
|
|
If upstream changes these parameters, adjust your duration expectations accordingly.
|
|||
|
|
|
|||
|
|
The framework enforces minimum durations (at least 2× slot duration), but be explicit. Very short runs risk false confidence—one lucky block doesn't prove liveness.
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
These principles keep scenarios:
|
|||
|
|
- **Portable** across environments (protocol time, declarative)
|
|||
|
|
- **Debuggable** (determinism, separation of concerns)
|
|||
|
|
- **Meaningful** (observable signals, sufficient duration)
|
|||
|
|
|
|||
|
|
When authoring scenarios, ask: "Does this test the protocol behavior or
|
|||
|
|
my local environment quirks?"
|