notify/file_transfer_state_test.go

372 lines
13 KiB
Go
Raw Normal View History

package notify
import (
itransfer "b612.me/notify/internal/transfer"
"testing"
"time"
)
func TestFileTransferStateObserveFeedsQuery(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1100, 0)
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "state-active", Size: 32},
Received: 10,
Total: 32,
Time: now,
})
state.observe(fileTransferDirectionReceive, FileEvent{
Kind: EnvelopeFileAbort,
Packet: FilePacket{FileID: "state-failed", Size: 16, Stage: "chunk"},
Received: 6,
Total: 16,
Time: now.Add(time.Second),
Err: errString("receive failed"),
})
active := state.active()
if got, want := len(active.Send), 1; got != want {
t.Fatalf("active send count mismatch: got %d want %d", got, want)
}
if got, want := active.Send[0].FileID, "state-active"; got != want {
t.Fatalf("active send fileID mismatch: got %q want %q", got, want)
}
failed := state.failed()
if got, want := len(failed.Receive), 1; got != want {
t.Fatalf("failed receive count mismatch: got %d want %d", got, want)
}
if got, want := failed.Receive[0].FileID, "state-failed"; got != want {
t.Fatalf("failed receive fileID mismatch: got %q want %q", got, want)
}
if got, want := failed.Receive[0].Failed, true; got != want {
t.Fatalf("failed receive flag mismatch: got %v want %v", got, want)
}
}
func TestFileTransferStateLatestHelpers(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1200, 0)
serverClient := &ClientConn{ClientID: "client-a"}
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "state-shared", Size: 40},
Received: 15,
Total: 40,
Time: now,
})
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileEnd,
Packet: FilePacket{FileID: "state-shared", Size: 40},
Received: 40,
Total: 40,
Done: true,
Time: now.Add(time.Second),
})
summary, ok := state.latest(fileTransferDirectionSend, clientFileScope(), "state-shared")
if !ok {
t.Fatal("latest send summary should exist")
}
if got, want := summary.Received, int64(15); got != want {
t.Fatalf("latest send received mismatch: got %d want %d", got, want)
}
group := state.latestByFileID("state-shared")
if got, want := len(group.Send), 1; got != want {
t.Fatalf("latest group send count mismatch: got %d want %d", got, want)
}
if got, want := len(group.Receive), 1; got != want {
t.Fatalf("latest group receive count mismatch: got %d want %d", got, want)
}
if got, want := group.Receive[0].Scope, serverFileScope(serverClient); got != want {
t.Fatalf("latest group receive scope mismatch: got %q want %q", got, want)
}
send := state.latestSendByFileID("state-shared")
if got, want := len(send), 1; got != want {
t.Fatalf("latest send list count mismatch: got %d want %d", got, want)
}
receive := state.latestReceiveByFileID("state-shared")
if got, want := len(receive), 1; got != want {
t.Fatalf("latest receive list count mismatch: got %d want %d", got, want)
}
if got, want := receive[0].Done, true; got != want {
t.Fatalf("latest receive done mismatch: got %v want %v", got, want)
}
}
func TestFileTransferStateObserveFeedsTransferRuntime(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1300, 0)
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "kernel-send", Name: "demo.bin", Size: 8, Checksum: "sum-send"},
Path: "/tmp/demo.bin",
Time: now,
})
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "kernel-send", Name: "demo.bin", Size: 8, Checksum: "sum-send"},
Received: 8,
Time: now.Add(time.Second),
})
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileEnd,
Packet: FilePacket{FileID: "kernel-send", Name: "demo.bin", Size: 8, Checksum: "sum-send"},
Received: 8,
Done: true,
Time: now.Add(2 * time.Second),
})
sendSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionSend, clientFileScope(), "kernel-send")
if !ok {
t.Fatal("send snapshot should exist")
}
if got, want := sendSnapshot.State, itransfer.StateDone; got != want {
t.Fatalf("send state = %v, want %v", got, want)
}
if got, want := sendSnapshot.Direction, itransfer.DirectionSend; got != want {
t.Fatalf("send direction = %v, want %v", got, want)
}
if got, want := sendSnapshot.SentBytes, int64(8); got != want {
t.Fatalf("send bytes = %d, want %d", got, want)
}
if got, want := sendSnapshot.AckedBytes, int64(8); got != want {
t.Fatalf("send acked bytes = %d, want %d", got, want)
}
if got := sendSnapshot.Metadata["name"]; got != "demo.bin" {
t.Fatalf("send metadata name = %q, want demo.bin", got)
}
state.observe(fileTransferDirectionReceive, FileEvent{
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "kernel-recv", Name: "recv.bin", Size: 6, Checksum: "sum-recv"},
Time: now,
})
state.observe(fileTransferDirectionReceive, FileEvent{
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "kernel-recv", Name: "recv.bin", Size: 6, Checksum: "sum-recv"},
Received: 6,
Time: now.Add(time.Second),
})
state.observe(fileTransferDirectionReceive, FileEvent{
Kind: EnvelopeFileEnd,
Packet: FilePacket{FileID: "kernel-recv", Name: "recv.bin", Size: 6, Checksum: "sum-recv"},
Received: 6,
Done: true,
Time: now.Add(2 * time.Second),
})
recvSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionReceive, clientFileScope(), "kernel-recv")
if !ok {
t.Fatal("receive snapshot should exist")
}
if got, want := recvSnapshot.State, itransfer.StateDone; got != want {
t.Fatalf("receive state = %v, want %v", got, want)
}
if got, want := recvSnapshot.Direction, itransfer.DirectionReceive; got != want {
t.Fatalf("receive direction = %v, want %v", got, want)
}
if got, want := recvSnapshot.ReceivedBytes, int64(6); got != want {
t.Fatalf("receive bytes = %d, want %d", got, want)
}
}
func TestFileTransferStateRuntimeResilienceStats(t *testing.T) {
state := newFileTransferState()
session := &fileSendSession{
fileID: "kernel-retry",
path: "/tmp/retry.bin",
name: "retry.bin",
size: 5,
checksum: "sum-retry",
}
state.startRuntimeSendSession(clientFileScope(), clientFileScope(), 0, session)
state.recordRuntimeTimeout(fileTransferDirectionSend, clientFileScope(), session.fileID)
state.recordRuntimeRetry(fileTransferDirectionSend, clientFileScope(), session.fileID)
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileAbort,
Packet: FilePacket{FileID: session.fileID, Name: session.name, Size: session.size, Checksum: session.checksum, Stage: "meta"},
Received: 0,
Err: errString("ack timeout"),
Time: time.Unix(1400, 0),
})
snapshot, ok := state.runtimeSnapshot(fileTransferDirectionSend, clientFileScope(), session.fileID)
if !ok {
t.Fatal("runtime snapshot should exist")
}
if got, want := snapshot.TimeoutCount, 1; got != want {
t.Fatalf("timeout count = %d, want %d", got, want)
}
if got, want := snapshot.RetryCount, 1; got != want {
t.Fatalf("retry count = %d, want %d", got, want)
}
if got, want := snapshot.State, itransfer.StateAborted; got != want {
t.Fatalf("state = %v, want %v", got, want)
}
if got, want := snapshot.LastError, "ack timeout"; got != want {
t.Fatalf("last error = %q, want %q", got, want)
}
if got, want := snapshot.Stage, "meta"; got != want {
t.Fatalf("stage = %q, want %q", got, want)
}
if got, want := snapshot.LastFailureStage, "meta"; got != want {
t.Fatalf("last failure stage = %q, want %q", got, want)
}
}
func TestFileTransferStateRuntimeSeparatesScopeAndDirection(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1450, 0)
serverClient := &ClientConn{ClientID: "client-b"}
state.observe(fileTransferDirectionSend, FileEvent{
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "shared-id", Name: "send.bin", Size: 4, Checksum: "sum-send"},
Time: now,
})
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "shared-id", Name: "recv.bin", Size: 6, Checksum: "sum-recv"},
Time: now.Add(time.Second),
})
sendSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionSend, clientFileScope(), "shared-id")
if !ok {
t.Fatal("send snapshot should exist")
}
if got, want := sendSnapshot.Direction, itransfer.DirectionSend; got != want {
t.Fatalf("send direction = %v, want %v", got, want)
}
recvSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionReceive, serverTransportScope(serverClient), "shared-id")
if !ok {
t.Fatal("receive snapshot should exist")
}
if got, want := recvSnapshot.Direction, itransfer.DirectionReceive; got != want {
t.Fatalf("receive direction = %v, want %v", got, want)
}
}
func TestFileTransferStateRuntimeSeparatesServerTransportGenerations(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1500, 0)
serverClient := &ClientConn{ClientID: "client-gen"}
serverClient.markClientConnIdentityBound()
serverClient.markClientConnStreamTransport()
serverClient.markClientConnTransportAttached()
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "shared-transfer", Name: "recv-a.bin", Size: 4, Checksum: "sum-a"},
Time: now,
})
firstScope := serverTransportScope(serverClient)
firstSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionReceive, firstScope, "shared-transfer")
if !ok {
t.Fatal("first generation snapshot should exist")
}
if got, want := firstSnapshot.Metadata[transferMetadataScopeKey], serverFileScope(serverClient); got != want {
t.Fatalf("first generation public scope metadata = %q, want %q", got, want)
}
serverClient.markClientConnTransportDetached("read error", nil)
serverClient.markClientConnTransportAttached()
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileMeta,
Packet: FilePacket{FileID: "shared-transfer", Name: "recv-b.bin", Size: 6, Checksum: "sum-b"},
Time: now.Add(time.Second),
})
secondScope := serverTransportScope(serverClient)
if secondScope == firstScope {
t.Fatalf("runtime scope should change across transport generations: got %q", secondScope)
}
secondSnapshot, ok := state.runtimeSnapshot(fileTransferDirectionReceive, secondScope, "shared-transfer")
if !ok {
t.Fatal("second generation snapshot should exist")
}
if got, want := transferSnapshotRuntimeScope(secondSnapshot.Metadata), secondScope; got != want {
t.Fatalf("second generation runtime scope metadata = %q, want %q", got, want)
}
if got, want := transferSnapshotTransportGeneration(secondSnapshot.Metadata), uint64(2); got != want {
t.Fatalf("second generation transport generation metadata = %d, want %d", got, want)
}
}
func TestFileTransferStateLatestByFileIDQueryResolvesTransportGeneration(t *testing.T) {
state := newFileTransferState()
now := time.Unix(1510, 0)
serverClient := &ClientConn{ClientID: "client-query-gen"}
serverClient.markClientConnIdentityBound()
serverClient.markClientConnStreamTransport()
serverClient.markClientConnTransportAttached()
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "shared", Size: 30},
Received: 6,
Total: 30,
Time: now,
})
firstRuntimeScope := serverTransportScope(serverClient)
logicalScope := serverFileScope(serverClient)
serverClient.markClientConnTransportDetached("read error", nil)
serverClient.markClientConnTransportAttached()
state.observe(fileTransferDirectionReceive, FileEvent{
ClientConn: serverClient,
Kind: EnvelopeFileChunk,
Packet: FilePacket{FileID: "shared", Size: 30},
Received: 10,
Total: 30,
Time: now.Add(time.Second),
})
secondRuntimeScope := serverTransportScope(serverClient)
legacy := state.latestReceiveByFileID("shared")
if got, want := len(legacy), 1; got != want {
t.Fatalf("legacy receive count mismatch: got %d want %d", got, want)
}
gen1 := state.latestReceiveByFileIDQuery("shared", fileTransferSummaryQuery{
Scope: logicalScope,
TransportGeneration: 1,
MatchTransportGeneration: true,
})
if got, want := len(gen1), 1; got != want {
t.Fatalf("generation-1 receive count mismatch: got %d want %d", got, want)
}
if got, want := gen1[0].RuntimeScope, firstRuntimeScope; got != want {
t.Fatalf("generation-1 runtime scope mismatch: got %q want %q", got, want)
}
gen2 := state.latestReceiveByFileIDQuery("shared", fileTransferSummaryQuery{
Scope: logicalScope,
TransportGeneration: 2,
MatchTransportGeneration: true,
})
if got, want := len(gen2), 1; got != want {
t.Fatalf("generation-2 receive count mismatch: got %d want %d", got, want)
}
if got, want := gen2[0].RuntimeScope, secondRuntimeScope; got != want {
t.Fatalf("generation-2 runtime scope mismatch: got %q want %q", got, want)
}
}