From d5a104e7c445d812e25a1ddfc2e6c629e01d89f8 Mon Sep 17 00:00:00 2001 From: Boming Zhang Date: Sun, 3 Mar 2024 18:02:51 -0500 Subject: [PATCH] feat: run single command in sandbox --- README.md | 10 +- cmd/joj3/main.go | 46 +++---- go.mod | 6 + go.sum | 17 +++ internal/executors/all.go | 1 + internal/executors/dummy/executor.go | 6 +- internal/executors/sandbox/convert.go | 176 +++++++++++++++++++++++++ internal/executors/sandbox/executor.go | 28 ++++ internal/executors/sandbox/grpc.go | 53 ++++++++ internal/executors/sandbox/meta.go | 14 ++ internal/executors/sandbox/util.go | 12 ++ internal/parsers/dummy/parser.go | 21 ++- internal/stage/executor.go | 2 +- internal/stage/model.go | 6 +- internal/stage/parser.go | 2 +- internal/stage/run.go | 26 +++- 16 files changed, 384 insertions(+), 42 deletions(-) create mode 100644 internal/executors/sandbox/convert.go create mode 100644 internal/executors/sandbox/executor.go create mode 100644 internal/executors/sandbox/grpc.go create mode 100644 internal/executors/sandbox/meta.go create mode 100644 internal/executors/sandbox/util.go diff --git a/README.md b/README.md index 3c17784..783949a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # JOJ3 +In order to register sandbox executor, you need to run go-judge before running this program. + ```bash $ make clean && make && ./build/joj3 rm -rf ./build/* rm -rf *.out go build -o ./build/joj3 ./cmd/joj3 -stage 0: score: 100, comment: dummy comment for stage 0 -stage 1: score: 101, comment: dummy comment for stage 1 +2024/03/03 18:01:11 INFO stage start name="stage 0" +2024/03/03 18:01:11 INFO sandbox run cmd="{Args:[ls] Env:[PATH=/usr/bin:/bin] Files:[0xc0000aa340 0xc0000aa380 0xc0000aa3c0] CPULimit:10000000000 RealCPULimit:0 ClockLimit:0 MemoryLimit:104857600 StackLimit:0 ProcLimit:50 CPURateLimit:0 CPUSetLimit: CopyIn:map[] CopyOut:[stdout stderr] CopyOutCached:[] CopyOutMax:0 CopyOutDir: TTY:false StrictMemoryLimit:false DataSegmentLimit:false AddressSpaceLimit:false}" +2024/03/03 18:01:11 INFO sandbox run ret="results:{status:Accepted time:1162000 runTime:3847400 memory:14929920 files:{key:\"stderr\" value:\"\"} files:{key:\"stdout\" value:\"stderr\\nstdout\\n\"}}" +2024/03/03 18:01:11 INFO executor done result="{Status:Accepted ExitStatus:0 Error: Time:1.162ms RunTime:3.8474ms Memory:14.2 MiB Files:map[stderr:len:0 stdout:len:14] FileIDs:map[] FileError:[]}" +2024/03/03 18:01:11 INFO parser done result="&{Score:100 Comment:dummy comment for stage 0, executor status: run time: 3847400 ns, memory: 14929920 bytes}" +stage 0: score: 100, comment: dummy comment for stage 0, executor status: run time: 3847400 ns, memory: 14929920 bytes ``` diff --git a/cmd/joj3/main.go b/cmd/joj3/main.go index 704d203..ea3b646 100644 --- a/cmd/joj3/main.go +++ b/cmd/joj3/main.go @@ -10,28 +10,30 @@ import ( func main() { tomlConfig := ` - [[stages]] - name = "stage 0" - [stages.executor] - name = "dummy" - [stages.executor.with] - args = [ "/usr/bin/cat", "/dev/null" ] - [stages.parser] - name = "dummy" - [stages.parser.with] - score = 100 - comment = "dummy comment for stage 0" - [[stages]] - name = "stage 1" - [stages.executor] - name = "dummy" - [stages.executor.with] - args = [ "/usr/bin/cat", "/dev/null" ] - [stages.parser] - name = "dummy" - [stages.parser.with] - score = 101 - comment = "dummy comment for stage 1" +[[stages]] +name = "stage 0" + [stages.executor] + name = "sandbox" + [stages.executor.with] + args = [ "ls" ] + env = [ "PATH=/usr/bin:/bin" ] + cpuLimit = 10_000_000_000 + memoryLimit = 104_857_600 + procLimit = 50 + copyOut = [ "stdout", "stderr" ] + [[stages.executor.with.files]] + content = "" + [[stages.executor.with.files]] + name = "stdout" + max = 4_096 + [[stages.executor.with.files]] + name = "stderr" + max = 4_096 + [stages.parser] + name = "dummy" + [stages.parser.with] + score = 100 + comment = "dummy comment for stage 0" ` stages := stage.ParseStages(tomlConfig) results := stage.Run(stages) diff --git a/go.mod b/go.mod index 453a292..8d56741 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,15 @@ go 1.22.0 require ( github.com/criyle/go-judge v1.8.1 github.com/mitchellh/mapstructure v1.5.0 + google.golang.org/grpc v1.61.0 ) require ( + github.com/golang/protobuf v1.5.3 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/protobuf v1.32.0 // indirect github.com/creack/pty v1.1.21 // indirect github.com/criyle/go-sandbox v0.10.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 diff --git a/go.sum b/go.sum index c864d72..5c9f939 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,10 @@ github.com/criyle/go-sandbox v0.10.1/go.mod h1:ivPw/HEh5unxVRlXJxCgkgTCuy+cxTkQD github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= @@ -20,10 +24,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/executors/all.go b/internal/executors/all.go index 706ba7b..bd860e6 100644 --- a/internal/executors/all.go +++ b/internal/executors/all.go @@ -2,6 +2,7 @@ package executors import ( _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy" + _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox" ) // this file does nothing but imports to ensure all the init() functions diff --git a/internal/executors/dummy/executor.go b/internal/executors/dummy/executor.go index 5c8b296..be0b09a 100644 --- a/internal/executors/dummy/executor.go +++ b/internal/executors/dummy/executor.go @@ -7,8 +7,8 @@ import ( type Dummy struct{} -func (e *Dummy) Run(model.Cmd) model.Result { - return model.Result{ +func (e *Dummy) Run(model.Cmd) (*model.Result, error) { + return &model.Result{ Status: model.Status(envexec.StatusInvalid), ExitStatus: 0, Error: "I'm a dummy", @@ -17,5 +17,5 @@ func (e *Dummy) Run(model.Cmd) model.Result { RunTime: 0, Files: map[string]string{}, FileIDs: map[string]string{}, - } + }, nil } diff --git a/internal/executors/sandbox/convert.go b/internal/executors/sandbox/convert.go new file mode 100644 index 0000000..0d147ee --- /dev/null +++ b/internal/executors/sandbox/convert.go @@ -0,0 +1,176 @@ +package sandbox + +import ( + "strings" + + "github.com/criyle/go-judge/cmd/go-judge/model" + "github.com/criyle/go-judge/pb" +) + +// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge-shell/grpc.go +func convertPBCmd(cmd []model.Cmd) []*pb.Request_CmdType { + var ret []*pb.Request_CmdType + for _, c := range cmd { + ret = append(ret, &pb.Request_CmdType{ + Args: c.Args, + Env: c.Env, + Tty: c.TTY, + Files: convertPBFiles(c.Files), + CpuTimeLimit: c.CPULimit, + ClockTimeLimit: c.ClockLimit, + MemoryLimit: c.MemoryLimit, + StackLimit: c.StackLimit, + ProcLimit: c.ProcLimit, + CpuRateLimit: c.CPURateLimit, + CpuSetLimit: c.CPUSetLimit, + DataSegmentLimit: c.DataSegmentLimit, + AddressSpaceLimit: c.AddressSpaceLimit, + CopyIn: convertPBCopyIn(c.CopyIn), + CopyOut: convertPBCopyOut(c.CopyOut), + CopyOutCached: convertPBCopyOut(c.CopyOutCached), + CopyOutMax: c.CopyOutMax, + CopyOutDir: c.CopyOutDir, + Symlinks: convertSymlink(c.CopyIn), + }) + } + return ret +} + +func convertPBCopyIn(copyIn map[string]model.CmdFile) map[string]*pb.Request_File { + rt := make(map[string]*pb.Request_File, len(copyIn)) + for k, i := range copyIn { + if i.Symlink != nil { + continue + } + rt[k] = convertPBFile(i) + } + return rt +} + +func convertPBCopyOut(copyOut []string) []*pb.Request_CmdCopyOutFile { + rt := make([]*pb.Request_CmdCopyOutFile, 0) + for _, n := range copyOut { + optional := false + if strings.HasSuffix(n, "?") { + optional = true + n = strings.TrimSuffix(n, "?") + } + rt = append(rt, &pb.Request_CmdCopyOutFile{ + Name: n, + Optional: optional, + }) + } + return rt +} + +func convertSymlink(copyIn map[string]model.CmdFile) map[string]string { + ret := make(map[string]string) + for k, v := range copyIn { + if v.Symlink == nil { + continue + } + ret[k] = *v.Symlink + } + return ret +} + +func convertPBFiles(files []*model.CmdFile) []*pb.Request_File { + var ret []*pb.Request_File + for _, f := range files { + if f == nil { + ret = append(ret, nil) + } else { + ret = append(ret, convertPBFile(*f)) + } + } + return ret +} + +func convertPBFile(i model.CmdFile) *pb.Request_File { + switch { + case i.Src != nil: + return &pb.Request_File{File: &pb.Request_File_Local{Local: &pb.Request_LocalFile{Src: *i.Src}}} + case i.Content != nil: + s := strToBytes(*i.Content) + return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}} + case i.FileID != nil: + return &pb.Request_File{File: &pb.Request_File_Cached{Cached: &pb.Request_CachedFile{FileID: *i.FileID}}} + case i.Name != nil && i.Max != nil: + return &pb.Request_File{File: &pb.Request_File_Pipe{Pipe: &pb.Request_PipeCollector{Name: *i.Name, Max: *i.Max, Pipe: i.Pipe}}} + case i.StreamIn: + return &pb.Request_File{File: &pb.Request_File_StreamIn{}} + case i.StreamOut: + return &pb.Request_File{File: &pb.Request_File_StreamOut{}} + } + return nil +} + +func convertPBPipeMapping(pm []model.PipeMap) []*pb.Request_PipeMap { + var ret []*pb.Request_PipeMap + for _, p := range pm { + ret = append(ret, &pb.Request_PipeMap{ + In: convertPBPipeIndex(p.In), + Out: convertPBPipeIndex(p.Out), + Name: p.Name, + Proxy: p.Proxy, + Max: uint64(p.Max), + }) + } + return ret +} + +func convertPBPipeIndex(pi model.PipeIndex) *pb.Request_PipeMap_PipeIndex { + return &pb.Request_PipeMap_PipeIndex{Index: int32(pi.Index), Fd: int32(pi.Fd)} +} + +func convertPBResult(res []*pb.Response_Result) []model.Result { + var ret []model.Result + for _, r := range res { + ret = append(ret, model.Result{ + Status: model.Status(r.Status), + ExitStatus: int(r.ExitStatus), + Error: r.Error, + Time: r.Time, + RunTime: r.RunTime, + Memory: r.Memory, + Files: convertFiles(r.Files), + Buffs: r.Files, + FileIDs: r.FileIDs, + FileError: convertPBFileError(r.FileError), + }) + } + return ret +} + +func convertFiles(buf map[string][]byte) map[string]string { + ret := make(map[string]string, len(buf)) + for k, v := range buf { + ret[k] = byteArrayToString(v) + } + return ret +} + +func convertPBRequest(req *model.Request) *pb.StreamRequest { + ret := &pb.StreamRequest{ + Request: &pb.StreamRequest_ExecRequest{ + ExecRequest: &pb.Request{ + RequestID: req.RequestID, + Cmd: convertPBCmd(req.Cmd), + PipeMapping: convertPBPipeMapping(req.PipeMapping), + }, + }, + } + return ret +} + +func convertPBFileError(fe []*pb.Response_FileError) []model.FileError { + var ret []model.FileError + for _, v := range fe { + ret = append(ret, model.FileError{ + Name: v.Name, + Type: model.FileErrorType(v.Type), + Message: v.Message, + }) + } + return ret +} diff --git a/internal/executors/sandbox/executor.go b/internal/executors/sandbox/executor.go new file mode 100644 index 0000000..24ee860 --- /dev/null +++ b/internal/executors/sandbox/executor.go @@ -0,0 +1,28 @@ +package sandbox + +import ( + "context" + "fmt" + "log/slog" + + "github.com/criyle/go-judge/cmd/go-judge/model" + "github.com/criyle/go-judge/pb" +) + +type Sandbox struct { + execClient pb.ExecutorClient +} + +func (e *Sandbox) Run(cmd model.Cmd) (*model.Result, error) { + slog.Info("sandbox run", "cmd", cmd) + req := &pb.Request{Cmd: convertPBCmd([]model.Cmd{cmd})} + ret, err := e.execClient.Exec(context.TODO(), req) + if err != nil { + return nil, err + } + if ret.Error != "" { + return nil, fmt.Errorf("compile error: %s", ret.Error) + } + slog.Info("sandbox run", "ret", ret) + return &convertPBResult(ret.Results)[0], nil +} diff --git a/internal/executors/sandbox/grpc.go b/internal/executors/sandbox/grpc.go new file mode 100644 index 0000000..8de65a2 --- /dev/null +++ b/internal/executors/sandbox/grpc.go @@ -0,0 +1,53 @@ +package sandbox + +import ( + "context" + "log/slog" + "os" + + "github.com/criyle/go-judge/pb" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +// copied from https://github.com/criyle/go-judger-demo/blob/master/apigateway/main.go +func createExecClient(execServer, token string) pb.ExecutorClient { + conn, err := createGRPCConnection(execServer, token) + if err != nil { + slog.Error("gRPC connection", "error", err) + os.Exit(1) + } + return pb.NewExecutorClient(conn) +} + +func createGRPCConnection(addr, token string) (*grpc.ClientConn, error) { + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + if token != "" { + opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token))) + } + return grpc.Dial(addr, opts...) +} + +type tokenAuth struct { + token string +} + +func newTokenAuth(token string) credentials.PerRPCCredentials { + return &tokenAuth{token: token} +} + +// Return value is mapped to request headers. +func (t *tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) ( + map[string]string, error, +) { + return map[string]string{ + "authorization": "Bearer " + t.token, + }, nil +} + +func (*tokenAuth) RequireTransportSecurity() bool { + return false +} diff --git a/internal/executors/sandbox/meta.go b/internal/executors/sandbox/meta.go new file mode 100644 index 0000000..a807a18 --- /dev/null +++ b/internal/executors/sandbox/meta.go @@ -0,0 +1,14 @@ +package sandbox + +import ( + "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" +) + +var name = "sandbox" + +func init() { + stage.RegisterExecutor(name, &Sandbox{ + // TODO: read from config + execClient: createExecClient("localhost:5051", ""), + }) +} diff --git a/internal/executors/sandbox/util.go b/internal/executors/sandbox/util.go new file mode 100644 index 0000000..4d9a17f --- /dev/null +++ b/internal/executors/sandbox/util.go @@ -0,0 +1,12 @@ +package sandbox + +import "unsafe" + +// faster with no memory copy +func strToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +func byteArrayToString(buf []byte) string { + return *(*string)(unsafe.Pointer(&buf)) +} diff --git a/internal/parsers/dummy/parser.go b/internal/parsers/dummy/parser.go index d25f843..5ee2619 100644 --- a/internal/parsers/dummy/parser.go +++ b/internal/parsers/dummy/parser.go @@ -1,6 +1,9 @@ package dummy import ( + "fmt" + "log/slog" + "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "github.com/criyle/go-judge/cmd/go-judge/model" "github.com/mitchellh/mapstructure" @@ -13,14 +16,20 @@ type Config struct { type Dummy struct{} -func (e *Dummy) Run(result model.Result, configAny any) stage.ParserResult { +func (e *Dummy) Run(result *model.Result, configAny any) ( + *stage.ParserResult, error, +) { var config Config err := mapstructure.Decode(configAny, &config) if err != nil { - panic(err) - } - return stage.ParserResult{ - Score: config.Score, - Comment: config.Comment, + slog.Error("failed to decode config", "err", err) + return nil, err } + return &stage.ParserResult{ + Score: config.Score, + Comment: fmt.Sprintf( + "%s, executor status: run time: %d ns, memory: %d bytes", + config.Comment, result.RunTime, result.Memory, + ), + }, nil } diff --git a/internal/stage/executor.go b/internal/stage/executor.go index ecf09d7..b1469c7 100644 --- a/internal/stage/executor.go +++ b/internal/stage/executor.go @@ -7,7 +7,7 @@ import ( var executorMap = map[string]Executor{} type Executor interface { - Run(model.Cmd) model.Result + Run(model.Cmd) (*model.Result, error) } func RegisterExecutor(name string, executor Executor) { diff --git a/internal/stage/model.go b/internal/stage/model.go index 199f65e..7815747 100644 --- a/internal/stage/model.go +++ b/internal/stage/model.go @@ -6,8 +6,10 @@ import ( type Stage struct { Name string + ExecutorName string Executor Executor ExecutorCmd model.Cmd + ParserName string Parser Parser ParserConfig any } @@ -19,7 +21,7 @@ type ParserResult struct { type StageResult struct { Name string - ParserResult + *ParserResult } type StagesConfig struct { @@ -27,7 +29,7 @@ type StagesConfig struct { Name string Executor struct { Name string - With interface{} + With model.Cmd } Parser struct { Name string diff --git a/internal/stage/parser.go b/internal/stage/parser.go index 0b572c1..6fbd9b3 100644 --- a/internal/stage/parser.go +++ b/internal/stage/parser.go @@ -5,7 +5,7 @@ import "github.com/criyle/go-judge/cmd/go-judge/model" var parserMap = map[string]Parser{} type Parser interface { - Run(model.Result, any) ParserResult + Run(*model.Result, any) (*ParserResult, error) } func RegisterParser(name string, parser Parser) { diff --git a/internal/stage/run.go b/internal/stage/run.go index 5ccfc52..734d966 100644 --- a/internal/stage/run.go +++ b/internal/stage/run.go @@ -1,7 +1,9 @@ package stage import ( - "github.com/criyle/go-judge/cmd/go-judge/model" + "log/slog" + "os" + "github.com/pelletier/go-toml/v2" ) @@ -9,14 +11,17 @@ func ParseStages(tomlConfig string) []Stage { var stagesConfig StagesConfig err := toml.Unmarshal([]byte(tomlConfig), &stagesConfig) if err != nil { - panic(err) + slog.Error("parse stages config", "error", err) + os.Exit(1) } stages := []Stage{} for _, stage := range stagesConfig.Stages { stages = append(stages, Stage{ Name: stage.Name, + ExecutorName: stage.Executor.Name, Executor: executorMap[stage.Executor.Name], - ExecutorCmd: model.Cmd{}, + ExecutorCmd: stage.Executor.With, + ParserName: stage.Parser.Name, Parser: parserMap[stage.Parser.Name], ParserConfig: stage.Parser.With, }) @@ -27,8 +32,19 @@ func ParseStages(tomlConfig string) []Stage { func Run(stages []Stage) []StageResult { var parserResults []StageResult for _, stage := range stages { - executorResult := stage.Executor.Run(stage.ExecutorCmd) - parserResult := stage.Parser.Run(executorResult, stage.ParserConfig) + slog.Info("stage start", "name", stage.Name) + executorResult, err := stage.Executor.Run(stage.ExecutorCmd) + if err != nil { + slog.Error("executor error", "name", stage.ExecutorName, "error", err) + break + } + slog.Info("executor done", "result", executorResult) + parserResult, err := stage.Parser.Run(executorResult, stage.ParserConfig) + if err != nil { + slog.Error("parser error", "name", stage.ExecutorName, "error", err) + break + } + slog.Info("parser done", "result", parserResult) parserResults = append(parserResults, StageResult{ Name: stage.Name, ParserResult: parserResult,