244 lines
11 KiB
Markdown
244 lines
11 KiB
Markdown
---
|
|
phase: 03-output-distribution
|
|
plan: 03
|
|
type: execute
|
|
wave: 2
|
|
depends_on: [03-01, 03-02]
|
|
files_modified:
|
|
- tcptop/tests/pipeline_test.rs
|
|
autonomous: false
|
|
requirements: [OPS-05]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Aggregator edge cases are tested: UDP idle timeout, TCP close lifecycle with PID inheritance, rate calculation across ticks, empty table tick"
|
|
- "Filtering logic is tested: port filter, pid filter, process name filter, tcp-only/udp-only filter"
|
|
- "All tests pass on macOS (no root, no Linux required)"
|
|
artifacts:
|
|
- path: "tcptop/tests/pipeline_test.rs"
|
|
provides: "Expanded test coverage for aggregator and filtering"
|
|
min_lines: 150
|
|
key_links:
|
|
- from: "tcptop/tests/pipeline_test.rs"
|
|
to: "tcptop/src/aggregator.rs"
|
|
via: "Tests exercise ConnectionTable update/tick edge cases"
|
|
pattern: "ConnectionTable::new"
|
|
---
|
|
|
|
<objective>
|
|
Expanded test coverage for core data processing and filtering logic, plus human verification on Linux VM.
|
|
|
|
Purpose: Fulfills OPS-05 (test coverage for core data processing and display logic) by expanding pipeline_test.rs with aggregator edge cases and filtering tests. Concludes with human verification of CSV logging and man page on the Linux VM.
|
|
Output: Expanded pipeline_test.rs with 8+ new tests, human-verified CSV and man page on Linux.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/Users/zrowitsch/local_src/tcptop/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/Users/zrowitsch/local_src/tcptop/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-output-distribution/03-01-SUMMARY.md
|
|
@.planning/phases/03-output-distribution/03-02-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- From tcptop/src/aggregator.rs: -->
|
|
```rust
|
|
pub struct ConnectionTable { ... }
|
|
impl ConnectionTable {
|
|
pub fn new() -> Self;
|
|
pub fn seed(&mut self, records: Vec<ConnectionRecord>);
|
|
pub fn update(&mut self, event: CollectorEvent);
|
|
pub fn tick(&mut self) -> (Vec<&ConnectionRecord>, Vec<ConnectionRecord>);
|
|
pub fn connection_count(&self) -> usize;
|
|
}
|
|
```
|
|
|
|
<!-- From tcptop/src/tui/app.rs (filtering): -->
|
|
```rust
|
|
pub struct CliFilters {
|
|
pub port: Option<u16>,
|
|
pub pid: Option<u32>,
|
|
pub process: Option<String>,
|
|
pub tcp_only: bool,
|
|
pub udp_only: bool,
|
|
}
|
|
pub struct App { ... }
|
|
impl App {
|
|
pub fn new(cli_filters: CliFilters) -> Self;
|
|
pub fn filter_records<'a>(&self, records: &[&'a ConnectionRecord], filter_text: &str) -> Vec<&'a ConnectionRecord>;
|
|
}
|
|
```
|
|
|
|
<!-- From tcptop/src/collector/mod.rs: -->
|
|
```rust
|
|
pub enum CollectorEvent {
|
|
TcpSend { key: ConnectionKey, pid: u32, comm: String, bytes: u32, srtt_us: u32 },
|
|
TcpRecv { key: ConnectionKey, pid: u32, comm: String, bytes: u32, srtt_us: u32 },
|
|
UdpSend { key: ConnectionKey, pid: u32, comm: String, bytes: u32 },
|
|
UdpRecv { key: ConnectionKey, pid: u32, comm: String, bytes: u32 },
|
|
TcpStateChange { key: ConnectionKey, pid: u32, old_state: u32, new_state: u32 },
|
|
}
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Expand pipeline_test.rs with aggregator edge cases and filtering tests</name>
|
|
<files>tcptop/tests/pipeline_test.rs</files>
|
|
<read_first>
|
|
- tcptop/tests/pipeline_test.rs (existing tests to extend)
|
|
- tcptop/src/aggregator.rs (ConnectionTable implementation, UDP_IDLE_TIMEOUT = 5s)
|
|
- tcptop/src/tui/app.rs (App::filter_records, CliFilters)
|
|
- tcptop/src/model.rs (ConnectionRecord, Protocol, TcpState)
|
|
- tcptop/src/collector/mod.rs (CollectorEvent variants)
|
|
</read_first>
|
|
<behavior>
|
|
- Test: UDP flow disappears from active list after idle timeout (>5 seconds since last_seen)
|
|
- Test: TCP state change with PID=0 inherits PID from earlier data event (PID enrichment)
|
|
- Test: Rate calculation is nonzero after two ticks with data between them
|
|
- Test: Empty table tick returns empty active and empty closed lists
|
|
- Test: Multiple connections can coexist in the same table (TCP + UDP)
|
|
- Test: CLI port filter includes connections matching source OR destination port
|
|
- Test: CLI pid filter includes only connections with matching PID
|
|
- Test: CLI tcp_only filter excludes UDP connections
|
|
- Test: Live text filter "/" matches process name substring
|
|
</behavior>
|
|
<action>
|
|
Add new tests to the EXISTING tcptop/tests/pipeline_test.rs file. Do NOT replace existing tests. Use the same helper pattern (inline ConnectionKey + CollectorEvent construction) as the existing tests.
|
|
|
|
**Aggregator tests to add:**
|
|
|
|
1. `test_udp_idle_timeout` - Create a UDP event, call `table.update()`, then use `std::thread::sleep(Duration::from_secs(6))` to exceed the 5-second idle timeout, then `table.tick()`. Assert `active.len() == 0`. (Note: this test is slow but proves the timeout works.)
|
|
|
|
2. `test_tcp_state_change_pid_inheritance` - Send TcpSend with pid=1234, then TcpStateChange with pid=0 and new_state=1 (ESTABLISHED). Tick. Assert the record still has pid=1234 (not 0). This tests the PID=0 enrichment logic from softirq context.
|
|
|
|
3. `test_rate_calculation_across_ticks` - Send TcpSend with bytes=1000. Call `table.tick()` (establishes baseline). Sleep briefly (`std::thread::sleep(Duration::from_millis(100))`). Send another TcpSend with bytes=2000 to same connection. Call `table.tick()` again. Assert `rate_out > 0.0` on the active record (proves rate calculation uses delta bytes / delta time).
|
|
|
|
4. `test_empty_table_tick` - Create empty ConnectionTable, call tick(). Assert `active.is_empty()` and `closed.is_empty()`.
|
|
|
|
5. `test_multiple_protocols_coexist` - Create TCP and UDP events with different keys. Tick. Assert `active.len() == 2`. Assert one has `protocol == Protocol::Tcp` and the other `protocol == Protocol::Udp`.
|
|
|
|
**Filtering tests to add** (using `tcptop::tui::app::{App, CliFilters}`):
|
|
|
|
Create a helper function `make_test_records() -> Vec<ConnectionRecord>` that returns 3 records:
|
|
- TCP connection: pid=100, process="nginx", local_port=80, remote_port=54321
|
|
- TCP connection: pid=200, process="curl", local_port=45678, remote_port=443
|
|
- UDP connection: pid=300, process="dns", local_port=53, remote_port=12345
|
|
|
|
6. `test_cli_port_filter` - Create App with CliFilters { port: Some(443), ..defaults }. Call `app.filter_records()` with all 3 records and empty filter_text. Assert result contains only the curl record (remote_port=443).
|
|
|
|
7. `test_cli_pid_filter` - CliFilters { pid: Some(100), ..defaults }. Assert result contains only nginx.
|
|
|
|
8. `test_cli_tcp_only_filter` - CliFilters { tcp_only: true, ..defaults }. Assert result contains 2 records (both TCP), no UDP.
|
|
|
|
9. `test_live_text_filter` - Create App with default CliFilters. Call `app.filter_records()` with filter_text="ngi". Assert result contains only nginx (process name substring match).
|
|
|
|
For filtering tests, construct ConnectionRecord manually (same as aggregator tests but with specific field values). The `last_seen` field can use `Instant::now()`, `prev_bytes_*` can be 0, `is_partial`/`is_closed` can be false.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /Users/zrowitsch/local_src/tcptop && cargo test --package tcptop --test pipeline_test -- --nocapture 2>&1 | tail -30</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- tcptop/tests/pipeline_test.rs contains `test_udp_idle_timeout`
|
|
- tcptop/tests/pipeline_test.rs contains `test_tcp_state_change_pid_inheritance`
|
|
- tcptop/tests/pipeline_test.rs contains `test_rate_calculation_across_ticks`
|
|
- tcptop/tests/pipeline_test.rs contains `test_empty_table_tick`
|
|
- tcptop/tests/pipeline_test.rs contains `test_multiple_protocols_coexist`
|
|
- tcptop/tests/pipeline_test.rs contains `test_cli_port_filter`
|
|
- tcptop/tests/pipeline_test.rs contains `test_cli_pid_filter`
|
|
- tcptop/tests/pipeline_test.rs contains `test_cli_tcp_only_filter`
|
|
- tcptop/tests/pipeline_test.rs contains `test_live_text_filter`
|
|
- `cargo test --package tcptop --test pipeline_test` exits 0
|
|
- tcptop/tests/pipeline_test.rs is at least 150 lines
|
|
</acceptance_criteria>
|
|
<done>9 new tests pass covering aggregator edge cases (UDP timeout, PID inheritance, rate calc, empty table, multi-protocol) and filtering logic (port, pid, tcp-only, live text filter)</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 2: Human verification of Phase 3 deliverables on Linux VM</name>
|
|
<files>n/a</files>
|
|
<action>
|
|
Human syncs code to dev-debian VM and verifies: (1) CSV headless logging produces valid output with header + data rows, (2) --help includes --log flag, (3) man page renders correctly with examples section, (4) all tests pass on Linux.
|
|
</action>
|
|
<verify>Human confirms all 5 verification steps below pass on the Linux VM.</verify>
|
|
<done>Human types "approved" confirming CSV logging, --help, man page, and test suite all work on Linux</done>
|
|
<what-built>
|
|
Complete Phase 3 deliverables: CSV headless logging (Plan 01), man page and packaging config (Plan 02), expanded test coverage (Plan 03 Task 1). Everything verified automatically on macOS. Now needs functional verification on the Linux VM.
|
|
</what-built>
|
|
<how-to-verify>
|
|
**Pre-step:** Sync code to dev-debian VM per reference_dev_debian.md.
|
|
|
|
**1. Build on Linux VM:**
|
|
```bash
|
|
cd ~/tcptop && cargo build --release 2>&1 | tail -5
|
|
```
|
|
|
|
**2. Test CSV logging (OUTP-01, OUTP-02):**
|
|
```bash
|
|
sudo ./target/release/tcptop --log /tmp/test.csv --interval 2 &
|
|
sleep 5
|
|
# Generate some traffic
|
|
curl -s https://example.com > /dev/null
|
|
sleep 3
|
|
kill %1
|
|
# Inspect CSV
|
|
head -5 /tmp/test.csv
|
|
# Verify: header row with 16 columns, data rows with timestamps
|
|
wc -l /tmp/test.csv
|
|
```
|
|
|
|
**3. Verify --help (OUTP-03):**
|
|
```bash
|
|
./target/release/tcptop --help
|
|
# Verify: --log flag appears in help output
|
|
```
|
|
|
|
**4. Verify man page (OUTP-04):**
|
|
```bash
|
|
man -l doc/tcptop.1
|
|
# Verify: renders correctly, EXAMPLES section visible, all flags documented
|
|
```
|
|
|
|
**5. Run all tests on Linux:**
|
|
```bash
|
|
cargo test --package tcptop 2>&1 | tail -20
|
|
# Verify: all tests pass including csv_test and expanded pipeline_test
|
|
```
|
|
|
|
**Expected outcomes:**
|
|
- CSV file has header row + data rows with connection info
|
|
- --help shows --log flag
|
|
- Man page renders with examples section
|
|
- All tests pass on Linux
|
|
</how-to-verify>
|
|
<resume-signal>Type "approved" or describe issues found during Linux VM testing</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
1. `cargo test --package tcptop --test pipeline_test` -- all 13+ tests pass (4 existing + 9 new)
|
|
2. `cargo test --package tcptop --test csv_test` -- all CSV tests pass
|
|
3. `cargo test --package tcptop` -- all package tests pass
|
|
4. Human verification on Linux VM confirms CSV logging, --help, man page, and test suite
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 9 new tests added to pipeline_test.rs covering aggregator edge cases and filtering
|
|
- All tests pass on macOS without root
|
|
- Human confirms CSV logging produces valid output on Linux VM
|
|
- Human confirms man page renders correctly
|
|
- Human confirms --help includes --log flag
|
|
- Human confirms all tests pass on Linux
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-output-distribution/03-03-SUMMARY.md`
|
|
</output>
|