130 lines
3.3 KiB
Go
130 lines
3.3 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/criyle/go-judge/pb"
|
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
)
|
|
|
|
type fakeExecutorClient struct {
|
|
pb.ExecutorClient
|
|
exec func(context.Context, *pb.Request) (*pb.Response, error)
|
|
fileDelete func(context.Context, *pb.FileID) (*emptypb.Empty, error)
|
|
}
|
|
|
|
func (f fakeExecutorClient) Exec(
|
|
ctx context.Context, req *pb.Request, _ ...grpc.CallOption,
|
|
) (*pb.Response, error) {
|
|
return f.exec(ctx, req)
|
|
}
|
|
|
|
func (f fakeExecutorClient) FileDelete(
|
|
ctx context.Context, id *pb.FileID, _ ...grpc.CallOption,
|
|
) (*emptypb.Empty, error) {
|
|
return f.fileDelete(ctx, id)
|
|
}
|
|
|
|
func TestExecRPCTimeout(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmds []stage.Cmd
|
|
want time.Duration
|
|
}{
|
|
{name: "margin only", want: rpcTimeoutMargin},
|
|
{
|
|
name: "largest clock limit",
|
|
cmds: []stage.Cmd{{ClockLimit: uint64(time.Minute)}, {ClockLimit: uint64(2 * time.Minute)}},
|
|
want: 2*time.Minute + rpcTimeoutMargin,
|
|
},
|
|
{
|
|
name: "cpu limit fallback",
|
|
cmds: []stage.Cmd{{CPULimit: uint64(time.Minute)}},
|
|
want: 2*time.Minute + rpcTimeoutMargin,
|
|
},
|
|
{
|
|
name: "clock limit takes precedence over cpu limit",
|
|
cmds: []stage.Cmd{{ClockLimit: uint64(time.Minute), CPULimit: uint64(10 * time.Minute)}},
|
|
want: time.Minute + rpcTimeoutMargin,
|
|
},
|
|
{
|
|
name: "cpu multiplication overflow",
|
|
cmds: []stage.Cmd{{CPULimit: math.MaxUint64}},
|
|
want: time.Duration(math.MaxInt64),
|
|
},
|
|
{
|
|
name: "duration overflow",
|
|
cmds: []stage.Cmd{{ClockLimit: math.MaxUint64}},
|
|
want: time.Duration(math.MaxInt64),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := execRPCTimeout(tt.cmds); got != tt.want {
|
|
t.Fatalf("execRPCTimeout() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunAppliesComputedRPCDeadline(t *testing.T) {
|
|
var remaining time.Duration
|
|
client := fakeExecutorClient{
|
|
exec: func(ctx context.Context, _ *pb.Request) (*pb.Response, error) {
|
|
deadline, ok := ctx.Deadline()
|
|
if !ok {
|
|
t.Fatal("Exec context has no deadline")
|
|
}
|
|
remaining = time.Until(deadline)
|
|
return &pb.Response{}, nil
|
|
},
|
|
}
|
|
executor := &Sandbox{
|
|
execClient: client,
|
|
cachedMap: make(map[string]string),
|
|
}
|
|
want := 2*time.Minute + rpcTimeoutMargin
|
|
_, err := executor.Run(context.Background(), []stage.Cmd{{
|
|
Args: []string{"true"},
|
|
CPULimit: uint64(time.Minute),
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if remaining > want || remaining < want-time.Second {
|
|
t.Fatalf("RPC deadline remaining = %v, want approximately %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestCleanupJoinsDeleteErrorsAndClearsCache(t *testing.T) {
|
|
deleteErr := errors.New("delete failed")
|
|
deleted := 0
|
|
client := fakeExecutorClient{
|
|
fileDelete: func(context.Context, *pb.FileID) (*emptypb.Empty, error) {
|
|
deleted++
|
|
return nil, deleteErr
|
|
},
|
|
}
|
|
executor := &Sandbox{
|
|
execClient: client,
|
|
cachedMap: map[string]string{"one": "1", "two": "2"},
|
|
}
|
|
err := executor.Cleanup(context.Background())
|
|
if !errors.Is(err, deleteErr) {
|
|
t.Fatalf("Cleanup() error = %v, want delete error", err)
|
|
}
|
|
if deleted != 2 {
|
|
t.Fatalf("FileDelete called %d times, want 2", deleted)
|
|
}
|
|
if len(executor.cachedMap) != 0 {
|
|
t.Fatalf("cached files not cleared: %v", executor.cachedMap)
|
|
}
|
|
}
|