woj-server/internal/service/runner/status.go

230 lines
5.1 KiB
Go
Raw Normal View History

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 TaskStatus struct {
Id int `json:"id"`
Points int32 `json:"points"`
RealTime int `json:"real_time"`
CpuTime int `json:"cpu_time"`
Memory int `json:"memory"`
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) getInfoText(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) getInfo() *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.RealTime = int(t.info["real_time"].(float64))
t.CpuTime = int(t.info["cpu_time"].(float64))
t.Memory = int(t.info["memory"].(float64))
}
return t
}
func (t *TaskStatus) checkExit() *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) checkTime(cLang *ConfigLanguage) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
if t.info["real_time"].(float64) > float64(cLang.Runtime.Run.TimeLimit)+5 {
t.Verdict = VerdictTimeLimitExceeded
t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.info["real_time"], t.info["cpu_time"])
}
return t
}
func (t *TaskStatus) checkMemory(cLang *ConfigLanguage) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
if t.info["memory"].(float64) > float64((cLang.Runtime.Run.MemoryLimit+1)*1024) {
t.Verdict = VerdictMemoryLimitExceeded
t.Message = fmt.Sprintf("memory: %v", t.info["memory"])
}
return t
}
func (t *TaskStatus) getJudgeText(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 file"
} else {
t.judgeText = string(j)
}
return t
}
func (t *TaskStatus) getJudge() *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 file"
}
return t
}
func (t *TaskStatus) checkJudge(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) (*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.getInfoText(info).
getInfo().
checkTime(meta.Cfg.Lang).
checkMemory(meta.Cfg.Lang).
checkExit().
getJudgeText(judge).
getJudge().
checkJudge(&pts)
sum += result.Points
results = append(results, result)
}
return &JudgeStatus{Message: "", Tasks: results}, sum
}