diff --git a/README.md b/README.md index 0ce5fb0..63e269d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ go build -o ./build/joj3 ./cmd/joj3 + cd ./_example/simple + ./../../build/joj3 + cat ./joj3_result.json -[{"Name":"compile","Score":100,"Comment":"compile done, executor status: run time: 239591301 ns, memory: 57176064 bytes"},{"Name":"run","Score":100,"Comment":"executor status: run time: 1839200 ns, memory: 16826368 bytes"}] +[{"Name":"compile","ParserResults":[{"Score":100,"Comment":"compile done, executor status: run time: 274901520 ns, memory: 57880576 bytes"}]},{"Name":"run","ParserResults":[{"Score":100,"Comment":"executor status: run time: 2343025 ns, memory: 13225984 bytes"},{"Score":0,"Comment":"executor status: run time: 2071433 ns, memory: 14544896 bytes"}]}] + rm -f ./joj3_result.json + cd - ``` diff --git a/_example/simple/2.stdin b/_example/simple/2.stdin new file mode 100644 index 0000000..b5f1e36 --- /dev/null +++ b/_example/simple/2.stdin @@ -0,0 +1 @@ +1024 2048 diff --git a/_example/simple/2.stdout b/_example/simple/2.stdout new file mode 100644 index 0000000..5fd86fd --- /dev/null +++ b/_example/simple/2.stdout @@ -0,0 +1 @@ +3072 diff --git a/_example/simple/conf.toml b/_example/simple/conf.toml index 2f27ff2..82fb253 100644 --- a/_example/simple/conf.toml +++ b/_example/simple/conf.toml @@ -3,7 +3,7 @@ logLevel = 8 name = "compile" [stages.executor] name = "sandbox" -[stages.executor.with] +[stages.executor.with.default] args = ["/usr/bin/g++", "a.cc", "-o", "a"] env = ["PATH=/usr/bin:/bin"] cpuLimit = 10_000_000_000 @@ -12,12 +12,12 @@ procLimit = 50 copyInCwd = true copyOut = ["stdout", "stderr"] copyOutCached = ["a"] -[[stages.executor.with.files]] +[[stages.executor.with.default.files]] content = "" -[[stages.executor.with.files]] +[[stages.executor.with.default.files]] name = "stdout" max = 4_096 -[[stages.executor.with.files]] +[[stages.executor.with.default.files]] name = "stderr" max = 4_096 [stages.parser] @@ -29,25 +29,38 @@ comment = "compile done" name = "run" [stages.executor] name = "sandbox" -[stages.executor.with] +[stages.executor.with.default] args = ["./a"] env = ["PATH=/usr/bin:/bin"] cpuLimit = 10_000_000_000 memoryLimit = 104_857_600 procLimit = 50 copyOut = ["stdout", "stderr"] -[stages.executor.with.copyInCached] +[stages.executor.with.default.copyInCached] a = "a" -[[stages.executor.with.files]] +[[stages.executor.with.cases]] +[[stages.executor.with.cases.files]] src = "1.stdin" -[[stages.executor.with.files]] +[[stages.executor.with.cases.files]] name = "stdout" max = 4_096 -[[stages.executor.with.files]] +[[stages.executor.with.cases.files]] +name = "stderr" +max = 4_096 +[[stages.executor.with.cases]] +[[stages.executor.with.cases.files]] +src = "1.stdin" +[[stages.executor.with.cases.files]] +name = "stdout" +max = 4_096 +[[stages.executor.with.cases.files]] name = "stderr" max = 4_096 [stages.parser] name = "diff" -[stages.parser.with] +[[stages.parser.with.cases]] score = 100 stdoutPath = "1.stdout" +[[stages.parser.with.cases]] +score = 100 +stdoutPath = "2.stdout" diff --git a/cmd/joj3/conf.go b/cmd/joj3/conf.go index ce68131..edd9b08 100644 --- a/cmd/joj3/conf.go +++ b/cmd/joj3/conf.go @@ -9,7 +9,10 @@ type Conf struct { Name string Executor struct { Name string - With stage.Cmd + With struct { + Default stage.Cmd + Cases []OptionalCmd + } } Parser struct { Name string @@ -17,3 +20,32 @@ type Conf struct { } } } + +type OptionalCmd struct { + Args *[]string + Env *[]string + Files *[]*stage.CmdFile + + CPULimit *uint64 + RealCPULimit *uint64 + ClockLimit *uint64 + MemoryLimit *uint64 + StackLimit *uint64 + ProcLimit *uint64 + CPURateLimit *uint64 + CPUSetLimit *string + + CopyIn *map[string]stage.CmdFile + CopyInCached *map[string]string + CopyInCwd *bool + + CopyOut *[]string + CopyOutCached *[]string + CopyOutMax *uint64 + CopyOutDir *string + + TTY *bool + StrictMemoryLimit *bool + DataSegmentLimit *bool + AddressSpaceLimit *bool +} diff --git a/cmd/joj3/main.go b/cmd/joj3/main.go index bdd0301..88ef7ec 100644 --- a/cmd/joj3/main.go +++ b/cmd/joj3/main.go @@ -10,15 +10,18 @@ import ( _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers" "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" - "github.com/pelletier/go-toml/v2" + // "github.com/pelletier/go-toml/v2" may panic on some error + "github.com/BurntSushi/toml" + "github.com/jinzhu/copier" ) -func parseConfFile(tomlPath *string) (Conf, []stage.Stage) { +func parseConfFile(tomlPath *string) Conf { tomlConfig, err := os.ReadFile(*tomlPath) if err != nil { slog.Error("read toml config", "error", err) os.Exit(1) } + // fill in default value of config file conf := Conf{ LogLevel: 0, OutputPath: "joj3_result.json", @@ -28,17 +31,7 @@ func parseConfFile(tomlPath *string) (Conf, []stage.Stage) { slog.Error("parse stages config", "error", err) os.Exit(1) } - stages := []stage.Stage{} - for _, s := range conf.Stages { - stages = append(stages, stage.Stage{ - Name: s.Name, - ExecutorName: s.Executor.Name, - ExecutorCmd: s.Executor.With, - ParserName: s.Parser.Name, - ParserConfig: s.Parser.With, - }) - } - return conf, stages + return conf } func setupSlog(conf Conf) { @@ -50,6 +43,30 @@ func setupSlog(conf Conf) { slog.SetDefault(logger) } +func generateStages(conf Conf) []stage.Stage { + stages := []stage.Stage{} + for _, s := range conf.Stages { + var cmds []stage.Cmd + for _, optionalCmd := range s.Executor.With.Cases { + cmd := s.Executor.With.Default + copier.Copy(&cmd, &optionalCmd) + cmds = append(cmds, cmd) + } + if len(s.Executor.With.Cases) == 0 { + cmds = append(cmds, s.Executor.With.Default) + } + slog.Info("parse stages config", "cmds", cmds) + stages = append(stages, stage.Stage{ + Name: s.Name, + ExecutorName: s.Executor.Name, + ExecutorCmds: cmds, + ParserName: s.Parser.Name, + ParserConfig: s.Parser.With, + }) + } + return stages +} + func outputResult(conf Conf, results []stage.StageResult) error { content, err := json.Marshal(results) if err != nil { @@ -62,8 +79,9 @@ func outputResult(conf Conf, results []stage.StageResult) error { func main() { tomlPath := flag.String("c", "conf.toml", "file path of the toml config") flag.Parse() - conf, stages := parseConfFile(tomlPath) + conf := parseConfFile(tomlPath) setupSlog(conf) + stages := generateStages(conf) defer stage.Cleanup() results := stage.Run(stages) err := outputResult(conf, results) diff --git a/go.mod b/go.mod index 9034edb..54355ad 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3 go 1.22.0 require ( + github.com/BurntSushi/toml v1.3.2 github.com/criyle/go-judge v1.8.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/pelletier/go-toml/v2 v2.1.1 google.golang.org/grpc v1.62.0 ) @@ -13,6 +13,7 @@ require ( github.com/creack/pty v1.1.21 // indirect github.com/criyle/go-sandbox v0.10.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/jinzhu/copier v0.4.0 golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 0fa0beb..f7db9a6 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,21 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/criyle/go-judge v1.8.2 h1:dGjLqJRBifqsLVZj1scr23zdM4wPe98HTIVgYzPuRxA= github.com/criyle/go-judge v1.8.2/go.mod h1:3RgsMp21D+UvXzkpOGsVFbLe2T2Lwk8jPEmCntQrvHQ= github.com/criyle/go-sandbox v0.10.1 h1:z9Il/UXQwKEvIwdr1wVheWWWAqGWtdTItBmEsWqFqT4= github.com/criyle/go-sandbox v0.10.1/go.mod h1:ivPw/HEh5unxVRlXJxCgkgTCuy+cxTkQDX7D2XQf/kg= -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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= 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= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= @@ -43,7 +33,3 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 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= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/executors/dummy/executor.go b/internal/executors/dummy/executor.go index 9764ac9..d272c58 100644 --- a/internal/executors/dummy/executor.go +++ b/internal/executors/dummy/executor.go @@ -7,17 +7,21 @@ import ( type Dummy struct{} -func (e *Dummy) Run(stage.Cmd) (*stage.ExecutorResult, error) { - return &stage.ExecutorResult{ - Status: stage.Status(envexec.StatusInvalid), - ExitStatus: 0, - Error: "I'm a dummy", - Time: 0, - Memory: 0, - RunTime: 0, - Files: map[string]string{}, - FileIDs: map[string]string{}, - }, nil +func (e *Dummy) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) { + var res []stage.ExecutorResult + for range cmds { + res = append(res, stage.ExecutorResult{ + Status: stage.Status(envexec.StatusInvalid), + ExitStatus: 0, + Error: "I'm a dummy", + Time: 0, + Memory: 0, + RunTime: 0, + Files: map[string]string{}, + FileIDs: map[string]string{}, + }) + } + return res, nil } func (e *Dummy) Cleanup() error { diff --git a/internal/executors/sandbox/executor.go b/internal/executors/sandbox/executor.go index d93f93f..6a61022 100644 --- a/internal/executors/sandbox/executor.go +++ b/internal/executors/sandbox/executor.go @@ -14,16 +14,20 @@ type Sandbox struct { cachedMap map[string]string } -func (e *Sandbox) Run(cmd stage.Cmd) (*stage.ExecutorResult, error) { - if cmd.CopyIn == nil { - cmd.CopyIn = make(map[string]stage.CmdFile) - } - for k, v := range cmd.CopyInCached { - if fileID, ok := e.cachedMap[v]; ok { - cmd.CopyIn[k] = stage.CmdFile{FileID: &fileID} +func (e *Sandbox) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) { + // cannot use range loop since we need to change the value + for i := 0; i < len(cmds); i++ { + cmd := &cmds[i] + if cmd.CopyIn == nil { + cmd.CopyIn = make(map[string]stage.CmdFile) + } + for k, v := range cmd.CopyInCached { + if fileID, ok := e.cachedMap[v]; ok { + cmd.CopyIn[k] = stage.CmdFile{FileID: &fileID} + } } } - pbReq := &pb.Request{Cmd: convertPBCmd([]stage.Cmd{cmd})} + pbReq := &pb.Request{Cmd: convertPBCmd(cmds)} pbRet, err := e.execClient.Exec(context.TODO(), pbReq) if err != nil { return nil, err @@ -31,11 +35,13 @@ func (e *Sandbox) Run(cmd stage.Cmd) (*stage.ExecutorResult, error) { if pbRet.Error != "" { return nil, fmt.Errorf("sandbox execute error: %s", pbRet.Error) } - executorRes := &convertPBResult(pbRet.Results)[0] - for fileName, fileID := range executorRes.FileIDs { - e.cachedMap[fileName] = fileID + results := convertPBResult(pbRet.Results) + for _, result := range results { + for fileName, fileID := range result.FileIDs { + e.cachedMap[fileName] = fileID + } } - return executorRes, nil + return results, nil } func (e *Sandbox) Cleanup() error { diff --git a/internal/parsers/diff/parser.go b/internal/parsers/diff/parser.go index b9cbc45..0608e50 100644 --- a/internal/parsers/diff/parser.go +++ b/internal/parsers/diff/parser.go @@ -8,33 +8,43 @@ import ( ) type Config struct { - Score int - StdoutPath string + Cases []struct { + Score int + StdoutPath string + } } type Diff struct{} -func (e *Diff) Run(result *stage.ExecutorResult, configAny any) ( - *stage.ParserResult, error, +func (e *Diff) Run(results []stage.ExecutorResult, configAny any) ( + []stage.ParserResult, error, ) { config, err := stage.DecodeConfig[Config](configAny) if err != nil { return nil, err } - score := 0 - stdout, err := os.ReadFile(config.StdoutPath) - if err != nil { - return nil, err + if len(config.Cases) != len(results) { + return nil, fmt.Errorf("cases number not match") } - // TODO: more compare strategies - if string(stdout) == result.Files["stdout"] { - score = config.Score + var res []stage.ParserResult + for i, caseConfig := range config.Cases { + result := results[i] + score := 0 + stdout, err := os.ReadFile(caseConfig.StdoutPath) + if err != nil { + return nil, err + } + // TODO: more compare strategies + if string(stdout) == result.Files["stdout"] { + score = caseConfig.Score + } + res = append(res, stage.ParserResult{ + Score: score, + Comment: fmt.Sprintf( + "executor status: run time: %d ns, memory: %d bytes", + result.RunTime, result.Memory, + ), + }) } - return &stage.ParserResult{ - Score: score, - Comment: fmt.Sprintf( - "executor status: run time: %d ns, memory: %d bytes", - result.RunTime, result.Memory, - ), - }, nil + return res, nil } diff --git a/internal/parsers/dummy/parser.go b/internal/parsers/dummy/parser.go index daa15a2..d1bd509 100644 --- a/internal/parsers/dummy/parser.go +++ b/internal/parsers/dummy/parser.go @@ -13,18 +13,22 @@ type Config struct { type Dummy struct{} -func (e *Dummy) Run(result *stage.ExecutorResult, configAny any) ( - *stage.ParserResult, error, +func (e *Dummy) Run(results []stage.ExecutorResult, configAny any) ( + []stage.ParserResult, error, ) { config, err := stage.DecodeConfig[Config](configAny) if err != nil { 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 + var res []stage.ParserResult + for _, result := range results { + res = append(res, stage.ParserResult{ + Score: config.Score, + Comment: fmt.Sprintf( + "%s, executor status: run time: %d ns, memory: %d bytes", + config.Comment, result.RunTime, result.Memory, + ), + }) + } + return res, nil } diff --git a/internal/stage/executor.go b/internal/stage/executor.go index e282462..2208d39 100644 --- a/internal/stage/executor.go +++ b/internal/stage/executor.go @@ -3,7 +3,7 @@ package stage var executorMap = map[string]Executor{} type Executor interface { - Run(Cmd) (*ExecutorResult, error) + Run([]Cmd) ([]ExecutorResult, error) Cleanup() error } diff --git a/internal/stage/model.go b/internal/stage/model.go index d97b3b7..62d1473 100644 --- a/internal/stage/model.go +++ b/internal/stage/model.go @@ -151,7 +151,7 @@ func (r ExecutorResult) String() string { type Stage struct { Name string ExecutorName string - ExecutorCmd Cmd + ExecutorCmds []Cmd ParserName string ParserConfig any } @@ -162,6 +162,6 @@ type ParserResult struct { } type StageResult struct { - Name string - *ParserResult + Name string + ParserResults []ParserResult } diff --git a/internal/stage/parser.go b/internal/stage/parser.go index 234a850..8a56a2c 100644 --- a/internal/stage/parser.go +++ b/internal/stage/parser.go @@ -3,7 +3,7 @@ package stage var parserMap = map[string]Parser{} type Parser interface { - Run(*ExecutorResult, any) (*ParserResult, error) + Run([]ExecutorResult, any) ([]ParserResult, error) } func RegisterParser(name string, parser Parser) { diff --git a/internal/stage/run.go b/internal/stage/run.go index 4e08450..0132c69 100644 --- a/internal/stage/run.go +++ b/internal/stage/run.go @@ -5,31 +5,31 @@ import ( ) func Run(stages []Stage) []StageResult { - var parserResults []StageResult + var stageResults []StageResult for _, stage := range stages { slog.Info("stage start", "name", stage.Name) - slog.Info("executor run start", "cmd", stage.ExecutorCmd) + slog.Info("executor run start", "cmds", stage.ExecutorCmds) executor := executorMap[stage.ExecutorName] - executorResult, err := executor.Run(stage.ExecutorCmd) + executorResults, err := executor.Run(stage.ExecutorCmds) if err != nil { slog.Error("executor run error", "name", stage.ExecutorName, "error", err) break } - slog.Info("executor run done", "result", executorResult) + slog.Info("executor run done", "results", executorResults) slog.Info("parser run start", "config", stage.ParserConfig) parser := parserMap[stage.ParserName] - parserResult, err := parser.Run(executorResult, stage.ParserConfig) + parserResults, err := parser.Run(executorResults, stage.ParserConfig) if err != nil { slog.Error("parser run error", "name", stage.ExecutorName, "error", err) break } - slog.Info("parser run done", "result", parserResult) - parserResults = append(parserResults, StageResult{ - Name: stage.Name, - ParserResult: parserResult, + slog.Info("parser run done", "results", parserResults) + stageResults = append(stageResults, StageResult{ + Name: stage.Name, + ParserResults: parserResults, }) } - return parserResults + return stageResults } func Cleanup() {