Skip to content

Commit 1f23f2f

Browse files
authored
Merge pull request #18 from MichailKon/dev/invoker/pipeline
Refactor of invoker compile and run pipeline
2 parents 8192ae1 + 79eb0c3 commit 1f23f2f

File tree

22 files changed

+882
-776
lines changed

22 files changed

+882
-776
lines changed

common/config/invoker.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type InvokerConfig struct {
2222

2323
CompilerConfigsFolder string `yaml:"CompilerConfigsFolder"`
2424

25-
CheckerLimits *RunConfig `yaml:"CheckerLimits,omitempty"`
25+
CheckerLimits *RunLimitsConfig `yaml:"CheckerLimits,omitempty"`
2626
}
2727

2828
func FillInInvokerConfig(config *InvokerConfig) {
@@ -63,7 +63,7 @@ func FillInInvokerConfig(config *InvokerConfig) {
6363
}
6464

6565
if config.CheckerLimits == nil {
66-
config.CheckerLimits = &RunConfig{}
66+
config.CheckerLimits = &RunLimitsConfig{}
6767
}
68-
fillInDefaultCheckerRunConfig(config.CheckerLimits)
68+
fillInDefaultCheckerRunLimitsConfig(config.CheckerLimits)
6969
}

common/config/run_config.go renamed to common/config/run_limits_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package config
22

33
import "testing_system/lib/customfields"
44

5-
type RunConfig struct {
5+
type RunLimitsConfig struct {
66
TimeLimit customfields.Time `yaml:"TimeLimit" json:"TimeLimit"`
77
MemoryLimit customfields.Memory `yaml:"MemoryLimit" json:"MemoryLimit"`
88
WallTimeLimit customfields.Time `yaml:"WallTimeLimit" json:"WallTimeLimit"`
@@ -21,7 +21,7 @@ type RunConfig struct {
2121
MaxOutputSize customfields.Memory `yaml:"MaxOutputSize" json:"MaxOutputSize"`
2222
}
2323

24-
func fillInDefaultCheckerRunConfig(config *RunConfig) {
24+
func fillInDefaultCheckerRunLimitsConfig(config *RunLimitsConfig) {
2525
if config.TimeLimit == 0 {
2626
config.TimeLimit.FromStr("15s")
2727
}

common/constants/verdict/verdict.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const (
88
PT Verdict = "PT" // Partial solution
99

1010
WA Verdict = "WA" // Wrong answer
11+
WR Verdict = "WR" // Wrong (used only for interactive problems)
1112

1213
RT Verdict = "RT" // Runtime error
1314
ML Verdict = "ML" // Memory limit

invoker/check_pipeline.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package invoker
2+
3+
import (
4+
"encoding/xml"
5+
"errors"
6+
"fmt"
7+
"golang.org/x/net/html/charset"
8+
"io"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"testing_system/common/connectors/storageconn"
13+
"testing_system/common/constants/resource"
14+
"testing_system/common/constants/verdict"
15+
"testing_system/invoker/sandbox"
16+
"testing_system/lib/logger"
17+
)
18+
19+
func (s *JobPipelineState) fullCheckPipeline() error {
20+
err := s.loadCheckerBinaryFile()
21+
if err != nil {
22+
return err
23+
}
24+
25+
err = s.loadTestAnswerFile()
26+
if err != nil {
27+
return err
28+
}
29+
30+
err = s.generateCheckerRunConfig()
31+
if err != nil {
32+
return err
33+
}
34+
35+
err = s.executeCheckerRunCommand()
36+
if err != nil {
37+
return err
38+
}
39+
40+
err = s.parseCheckerResult()
41+
if err != nil {
42+
return err
43+
}
44+
return nil
45+
}
46+
47+
func (s *JobPipelineState) generateCheckerRunConfig() error {
48+
s.test.checkConfig = &sandbox.ExecuteConfig{
49+
RunLimitsConfig: *s.invoker.TS.Config.Invoker.CheckerLimits,
50+
}
51+
52+
s.test.checkConfig.Command = checkerBinaryFile
53+
s.test.checkConfig.Args = []string{
54+
testInputFile, testOutputFile, testAnswerFile, checkResultFile, checkResultFileArg,
55+
}
56+
logger.Trace("Generated checker run config for %s", s.loggerData)
57+
return nil
58+
}
59+
60+
func (s *JobPipelineState) executeCheckerRunCommand() error {
61+
s.executeWaitGroup.Add(1)
62+
s.invoker.RunQueue <- s.runChecker
63+
s.executeWaitGroup.Wait()
64+
65+
if s.test.checkResult.Err != nil {
66+
return fmt.Errorf("can not run checker in sandbox, error: %v", s.test.checkResult.Err)
67+
}
68+
69+
switch s.test.checkResult.Verdict {
70+
case verdict.OK, verdict.RT:
71+
logger.Trace("Finished checker run for %s", s.loggerData)
72+
return nil
73+
case verdict.TL:
74+
return fmt.Errorf("checker running took more than %v time", s.test.checkConfig.TimeLimit)
75+
case verdict.ML:
76+
return fmt.Errorf("checker running took more than %v memory", s.test.checkConfig.MemoryLimit)
77+
case verdict.WL:
78+
return fmt.Errorf("checker running took more than %v wall time", s.test.checkConfig.WallTimeLimit)
79+
case verdict.SE:
80+
return fmt.Errorf("checker security violation")
81+
default:
82+
return fmt.Errorf("unknown checker sandbox run verdict: %s", s.test.checkResult.Verdict)
83+
}
84+
}
85+
86+
func (s *JobPipelineState) runChecker() {
87+
s.test.checkResult = s.sandbox.Run(s.test.checkConfig)
88+
s.executeWaitGroup.Done()
89+
}
90+
91+
func (s *JobPipelineState) parseCheckerResult() error {
92+
_, err := os.Stat(filepath.Join(s.sandbox.Dir(), checkResultFile))
93+
if err == nil {
94+
return s.parseTestlibCheckerResult()
95+
} else if errors.Is(err, os.ErrNotExist) {
96+
return fmt.Errorf("no checker result file found, non testlib checkers are not supported")
97+
} else {
98+
return fmt.Errorf("can not stat checker result file")
99+
}
100+
}
101+
102+
func (s *JobPipelineState) parseTestlibCheckerResult() error {
103+
checkResultReader, err := os.Open(filepath.Join(s.sandbox.Dir(), checkResultFile))
104+
if err != nil {
105+
return fmt.Errorf(
106+
"checker exited with exit code %d, can not parse checker result xml file in appes mode",
107+
s.test.checkResult.Statistics.ExitCode,
108+
)
109+
}
110+
defer checkResultReader.Close()
111+
112+
var checkerResult CheckerResultXML
113+
xmlReader := xml.NewDecoder(checkResultReader)
114+
xmlReader.CharsetReader = charset.NewReaderLabel
115+
err = xmlReader.Decode(&checkerResult)
116+
if err != nil {
117+
return fmt.Errorf(
118+
"can not parse checker result xml file in appes mode: %s",
119+
err.Error(),
120+
)
121+
}
122+
123+
s.test.checkerOutputReader = s.limitedReader(strings.NewReader(checkerResult.Value))
124+
switch checkerResult.Outcome {
125+
case "accepted":
126+
s.test.runResult.Verdict = verdict.OK
127+
case "wrong-answer", "presentation-error", "unexpected-eof": // We will treat PE as WA as all testing systems now do
128+
s.test.runResult.Verdict = verdict.WA
129+
case "fail":
130+
// Only in case of checker CF verdict we accept job as successful
131+
s.test.runResult.Verdict = verdict.CF
132+
case "points", "relative-scoring":
133+
if checkerResult.Points == nil {
134+
return fmt.Errorf(
135+
"checker exited with exit code %d and verdict %s, but no points specified",
136+
s.test.checkResult.Statistics.ExitCode,
137+
checkerResult.Outcome,
138+
)
139+
} else {
140+
s.test.runResult.Verdict = verdict.PT
141+
s.test.runResult.Points = checkerResult.Points
142+
}
143+
default:
144+
return fmt.Errorf(
145+
"unknown checker verdict %s, checker exited with exit code %d",
146+
checkerResult.Outcome,
147+
s.test.checkResult.Statistics.ExitCode,
148+
)
149+
}
150+
151+
logger.Trace(
152+
"Parsed testlib checker result for %s, checker verdict is %s",
153+
s.loggerData, s.test.checkResult.Verdict,
154+
)
155+
return nil
156+
}
157+
158+
func (s *JobPipelineState) uploadTestRunResources() error {
159+
err := s.uploadOutput(testOutputFile, resource.TestOutput)
160+
if err != nil {
161+
return err
162+
}
163+
164+
err = s.uploadOutput(testErrorFile, resource.TestStderr)
165+
if err != nil {
166+
return err
167+
}
168+
169+
err = s.uploadCheckerOutput()
170+
if err != nil {
171+
return err
172+
}
173+
return nil
174+
}
175+
176+
func (s *JobPipelineState) uploadCheckerOutput() error {
177+
checkerOutputRequest := &storageconn.Request{
178+
Resource: resource.CheckerOutput,
179+
SubmitID: uint64(s.job.Submission.ID),
180+
TestID: s.job.Test,
181+
Files: map[string]io.Reader{
182+
checkOutputFile: s.test.checkerOutputReader,
183+
},
184+
}
185+
resp := s.invoker.TS.StorageConn.Upload(checkerOutputRequest)
186+
if resp.Error != nil {
187+
return fmt.Errorf("can not upload checker output to storage, error: %s", resp.Error.Error())
188+
}
189+
logger.Trace("Uploaded checker output for %s", s.loggerData)
190+
return nil
191+
}
192+
193+
type CheckerResultXML struct {
194+
Outcome string `xml:"outcome,attr"`
195+
Points *float64 `xml:"points,attr,omitempty"`
196+
Value string `xml:",chardata"`
197+
}

0 commit comments

Comments
 (0)