372 lines
13 KiB
Go
372 lines
13 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|