package runner import ( "bytes" "encoding/json" "encoding/xml" "fmt" "git.0x7f.app/WOJ/woj-server/pkg/file" "golang.org/x/text/encoding/charmap" "io" "path/filepath" ) const ( VerdictAccepted = iota VerdictWrongAnswer VerdictJuryFailed VerdictPartialCorrect VerdictTimeLimitExceeded VerdictMemoryLimitExceeded VerdictRuntimeError VerdictCompileError VerdictSystemError ) type TestLibReport struct { XMLName xml.Name `xml:"result"` Outcome string `xml:"outcome,attr"` PCType int `xml:"pctype,attr"` Points float64 `xml:"points,attr"` Result string `xml:",chardata"` } type RuntimeStatus struct { RealTime int `json:"real_time"` // in ms CpuTime int `json:"cpu_time"` // in ms Memory int `json:"memory"` // in kb } type TaskStatus struct { Id int `json:"id"` Points int32 `json:"points"` Runtime RuntimeStatus `json:"runtime"` Verdict int `json:"verdict"` Message string `json:"message"` infoText []byte info map[string]interface{} judgeText string judge TestLibReport } type JudgeStatus struct { Message string `json:"message"` CompileMessage string `json:"compile_message"` Tasks []TaskStatus `json:"tasks"` } func (t *TaskStatus) ReadSandboxInfo(infoFile string) *TaskStatus { if t.Verdict != VerdictAccepted { return t } var err error t.infoText, err = file.Read(infoFile) if err != nil { t.Verdict = VerdictSystemError t.Message = "cannot read info file" } return t } func (t *TaskStatus) ExtractSandboxInfo() *TaskStatus { if t.Verdict != VerdictAccepted { return t } err := json.Unmarshal(t.infoText, &t.info) if err != nil { t.Verdict = VerdictSystemError t.Message = "cannot parse info file" } else { t.Runtime = RuntimeStatus{ RealTime: int(t.info["real_time"].(float64)), CpuTime: int(t.info["cpu_time"].(float64)), Memory: int(t.info["memory"].(float64)), } } return t } func (t *TaskStatus) MergeContainerInfo(status *RuntimeStatus) *TaskStatus { if t.Verdict != VerdictAccepted { return t } t.Runtime.RealTime = max(t.Runtime.RealTime, status.RealTime) t.Runtime.CpuTime = max(t.Runtime.CpuTime, status.CpuTime) t.Runtime.Memory = max(t.Runtime.Memory, status.Memory) return t } func (t *TaskStatus) CheckTime(cLang *ConfigLanguage) *TaskStatus { if t.Verdict != VerdictAccepted { return t } if t.Runtime.RealTime > cLang.Runtime.Run.TimeLimit+5 || t.Runtime.CpuTime > cLang.Runtime.Run.TimeLimit+5 { t.Verdict = VerdictTimeLimitExceeded t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.Runtime.RealTime, t.Runtime.CpuTime) } return t } func (t *TaskStatus) CheckMemory(cLang *ConfigLanguage) *TaskStatus { if t.Verdict != VerdictAccepted { return t } // t.Runtime.Memory is in kb if t.Runtime.Memory > (cLang.Runtime.Run.MemoryLimit+1)*1024 { t.Verdict = VerdictMemoryLimitExceeded t.Message = fmt.Sprintf("memory: %v", t.Runtime.Memory) } return t } func (t *TaskStatus) CheckExitCode() *TaskStatus { if t.Verdict != VerdictAccepted { return t } if t.info["status"] != "exited" || t.info["code"] != 0.0 { t.Verdict = VerdictRuntimeError t.Message = fmt.Sprintf("status: %v, code: %v", t.info["status"], t.info["code"]) } return t } func (t *TaskStatus) ReadJudgeReport(judgeFile string) *TaskStatus { if t.Verdict != VerdictAccepted { return t } j, err := file.Read(judgeFile) if err != nil { t.Verdict = VerdictSystemError t.Message = "cannot read judge report" } else { t.judgeText = string(j) } return t } func (t *TaskStatus) DecodeJudgeReport() *TaskStatus { if t.Verdict != VerdictAccepted { return t } b := bytes.NewReader([]byte(t.judgeText)) d := xml.NewDecoder(b) d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { switch charset { case "windows-1251": return charmap.Windows1251.NewDecoder().Reader(input), nil default: return nil, fmt.Errorf("unknown charset: %s", charset) } } err := d.Decode(&t.judge) if err != nil { t.Verdict = VerdictSystemError t.Message = "cannot parse judge report" } return t } func (t *TaskStatus) CheckJudgeReport(pts *map[int]int32) *TaskStatus { if t.Verdict != VerdictAccepted { return t } mp := map[string]int{ "accepted": VerdictAccepted, "wrong-answer": VerdictWrongAnswer, "presentation-error": VerdictWrongAnswer, "points": VerdictPartialCorrect, "relative-scoring": VerdictPartialCorrect, } if v, ok := mp[t.judge.Outcome]; ok { t.Verdict = v t.Message = t.judge.Result if v == VerdictAccepted { t.Points = (*pts)[t.Id] } else if v == VerdictPartialCorrect { t.Points = int32(t.judge.Points) + int32(t.judge.PCType) } } else { t.Verdict = VerdictJuryFailed t.Message = fmt.Sprintf("unknown outcome: %v, result: %v", t.judge.Outcome, t.judge.Result) } return t } func (s *service) CheckResults(meta *JudgeMeta, prResults ProblemRunResults) (*JudgeStatus, int32) { // CE will be processed in phase compile pts := map[int]int32{} for _, task := range meta.Cfg.All.Tasks { pts[task.Id] = task.Points } var results []TaskStatus dir := filepath.Join(UserDir, meta.Run.User) var sum int32 = 0 for i := 1; i <= len(meta.Cfg.All.Tasks); i++ { result := TaskStatus{Id: i, Verdict: VerdictAccepted, Points: 0} info := filepath.Join(dir, fmt.Sprintf("%d.info", i)) judge := filepath.Join(dir, fmt.Sprintf("%d.judge", i)) result.ReadSandboxInfo(info). ExtractSandboxInfo(). MergeContainerInfo(&prResults[i].Status). CheckTime(meta.Cfg.Lang). CheckMemory(meta.Cfg.Lang). CheckExitCode(). ReadJudgeReport(judge). DecodeJudgeReport(). CheckJudgeReport(&pts) sum += result.Points results = append(results, result) } return &JudgeStatus{Message: "", Tasks: results}, sum }