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

251 lines
5.7 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 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: t.info["real_time"].(int),
CpuTime: t.info["cpu_time"].(int),
Memory: t.info["memory"].(int),
}
}
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 {
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
}