mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-08 19:06:14 -08:00
* chore(go): use lowercase module import path * ci: scope checks for Go module changes
165 lines
4.4 KiB
Go
165 lines
4.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/easytier/easytier/easytier-go/internal/coreabi"
|
|
)
|
|
|
|
type recordingCore struct {
|
|
calls []string
|
|
}
|
|
|
|
func (*recordingCore) Start(context.Context) error { return nil }
|
|
func (*recordingCore) Stop(context.Context) error { return nil }
|
|
func (core *recordingCore) Drive(context.Context) (coreabi.State, error) {
|
|
core.calls = append(core.calls, "drive")
|
|
return coreabi.StateRunning, nil
|
|
}
|
|
func (core *recordingCore) NotifyCompletions(context.Context) error {
|
|
core.calls = append(core.calls, "notify")
|
|
return nil
|
|
}
|
|
func (core *recordingCore) NextDeadline(context.Context) (int64, error) {
|
|
core.calls = append(core.calls, "deadline")
|
|
return math.MaxInt64, nil
|
|
}
|
|
func (core *recordingCore) SendPacket(context.Context, []byte) error {
|
|
core.calls = append(core.calls, "send")
|
|
return nil
|
|
}
|
|
func (*recordingCore) Drop(context.Context) error { return nil }
|
|
|
|
func TestCompletionNotifiesGuestBeforeDriving(t *testing.T) {
|
|
core := &recordingCore{}
|
|
instance := &Instance{
|
|
host: &Host{},
|
|
ctx: context.Background(),
|
|
core: core,
|
|
running: make(chan struct{}),
|
|
stopped: make(chan struct{}),
|
|
}
|
|
if _, err := instance.drive(true); err != nil {
|
|
t.Fatalf("drive completion: %v", err)
|
|
}
|
|
want := []string{"notify", "drive", "deadline"}
|
|
if !reflect.DeepEqual(core.calls, want) {
|
|
t.Fatalf("completion call order = %v, want %v", core.calls, want)
|
|
}
|
|
}
|
|
|
|
func TestPacketIngressBatchDrivesOnce(t *testing.T) {
|
|
core := &recordingCore{}
|
|
instance := &Instance{
|
|
host: &Host{},
|
|
ctx: context.Background(),
|
|
core: core,
|
|
commands: make(chan command, maximumPacketIngressBatch),
|
|
running: make(chan struct{}),
|
|
stopped: make(chan struct{}),
|
|
}
|
|
requests := make([]command, 3)
|
|
for index := range requests {
|
|
requests[index] = command{
|
|
kind: commandSendPacket,
|
|
packet: []byte{byte(index)},
|
|
response: make(chan error, 1),
|
|
}
|
|
if index != 0 {
|
|
instance.commands <- requests[index]
|
|
}
|
|
}
|
|
|
|
if _, err := instance.handlePacketBatch(requests[0], math.MaxInt64); err != nil {
|
|
t.Fatalf("handle packet batch: %v", err)
|
|
}
|
|
for index := range requests {
|
|
if err := <-requests[index].response; err != nil {
|
|
t.Fatalf("packet %d response: %v", index, err)
|
|
}
|
|
}
|
|
want := []string{"send", "send", "send", "drive", "deadline"}
|
|
if !reflect.DeepEqual(core.calls, want) {
|
|
t.Fatalf("packet batch call order = %v, want %v", core.calls, want)
|
|
}
|
|
}
|
|
|
|
func TestSendPacketBorrowsBufferUntilGuestConsumesIt(t *testing.T) {
|
|
instance := &Instance{
|
|
commands: make(chan command, 1),
|
|
closeRequested: make(chan struct{}),
|
|
done: make(chan struct{}),
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
packet := []byte{1, 2, 3}
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
result <- instance.SendPacket(ctx, packet)
|
|
}()
|
|
|
|
var request command
|
|
select {
|
|
case request = <-instance.commands:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("SendPacket did not enqueue")
|
|
}
|
|
if &request.packet[0] != &packet[0] {
|
|
t.Fatal("SendPacket copied the packet before enqueue")
|
|
}
|
|
|
|
cancel()
|
|
select {
|
|
case err := <-result:
|
|
t.Fatalf("SendPacket returned before guest consumption: %v", err)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
|
|
request.response <- nil
|
|
select {
|
|
case err := <-result:
|
|
if err != nil {
|
|
t.Fatalf("SendPacket after guest consumption: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("SendPacket did not return after guest consumption")
|
|
}
|
|
}
|
|
|
|
func TestSendPacketBackpressureRemainsCancelableBeforeEnqueue(t *testing.T) {
|
|
instance := &Instance{
|
|
commands: make(chan command, 1),
|
|
closeRequested: make(chan struct{}),
|
|
done: make(chan struct{}),
|
|
}
|
|
instance.commands <- command{kind: commandStart}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
result <- instance.SendPacket(ctx, []byte{1})
|
|
}()
|
|
|
|
select {
|
|
case err := <-result:
|
|
t.Fatalf("SendPacket bypassed full command queue: %v", err)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
cancel()
|
|
select {
|
|
case err := <-result:
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("SendPacket on full queue returned %v, want context cancellation", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("SendPacket on full queue ignored context cancellation")
|
|
}
|
|
if got := len(instance.commands); got != 1 {
|
|
t.Fatalf("queued commands = %d, want the original command only", got)
|
|
}
|
|
}
|