2022-10-22 17:38:39 +08:00
|
|
|
package runner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
|
|
|
"fmt"
|
2024-01-06 16:06:16 +08:00
|
|
|
"git.0x7f.app/WOJ/woj-server/pkg/file"
|
2022-10-22 17:38:39 +08:00
|
|
|
"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"`
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
type RuntimeStatus struct {
|
|
|
|
RealTime int `json:"real_time"` // in ms
|
|
|
|
CpuTime int `json:"cpu_time"` // in ms
|
|
|
|
Memory int `json:"memory"` // in kb
|
|
|
|
}
|
|
|
|
|
2022-10-22 17:38:39 +08:00
|
|
|
type TaskStatus struct {
|
2024-02-15 12:53:57 +08:00
|
|
|
Id int `json:"id"`
|
|
|
|
Points int32 `json:"points"`
|
|
|
|
Runtime RuntimeStatus `json:"runtime"`
|
|
|
|
Verdict int `json:"verdict"`
|
|
|
|
Message string `json:"message"`
|
2022-10-22 17:38:39 +08:00
|
|
|
|
|
|
|
infoText []byte
|
|
|
|
info map[string]interface{}
|
|
|
|
judgeText string
|
|
|
|
judge TestLibReport
|
|
|
|
}
|
|
|
|
|
|
|
|
type JudgeStatus struct {
|
2024-01-29 21:15:39 +08:00
|
|
|
Message string `json:"message"`
|
|
|
|
CompileMessage string `json:"compile_message"`
|
|
|
|
Tasks []TaskStatus `json:"tasks"`
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) ReadSandboxInfo(infoFile string) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2024-01-06 17:31:00 +08:00
|
|
|
t.infoText, err = file.Read(infoFile)
|
2022-10-22 17:38:39 +08:00
|
|
|
if err != nil {
|
|
|
|
t.Verdict = VerdictSystemError
|
|
|
|
t.Message = "cannot read info file"
|
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) ExtractSandboxInfo() *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
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 {
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Runtime = RuntimeStatus{
|
2024-02-19 21:27:27 +08:00
|
|
|
RealTime: int(t.info["real_time"].(float64)),
|
|
|
|
CpuTime: int(t.info["cpu_time"].(float64)),
|
|
|
|
Memory: int(t.info["memory"].(float64)),
|
2024-02-15 12:53:57 +08:00
|
|
|
}
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) MergeContainerInfo(status *RuntimeStatus) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
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)
|
2022-10-22 17:38:39 +08:00
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) CheckTime(cLang *ConfigLanguage) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
if t.Runtime.RealTime > cLang.Runtime.Run.TimeLimit+5 ||
|
|
|
|
t.Runtime.CpuTime > cLang.Runtime.Run.TimeLimit+5 {
|
2022-10-22 17:38:39 +08:00
|
|
|
t.Verdict = VerdictTimeLimitExceeded
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.Runtime.RealTime, t.Runtime.CpuTime)
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) CheckMemory(cLang *ConfigLanguage) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
// t.Runtime.Memory is in kb
|
|
|
|
if t.Runtime.Memory > (cLang.Runtime.Run.MemoryLimit+1)*1024 {
|
2022-10-22 17:38:39 +08:00
|
|
|
t.Verdict = VerdictMemoryLimitExceeded
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Message = fmt.Sprintf("memory: %v", t.Runtime.Memory)
|
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskStatus) CheckExitCode() *TaskStatus {
|
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-19 21:27:27 +08:00
|
|
|
if t.info["status"] != "exited" || t.info["code"] != 0.0 {
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Verdict = VerdictRuntimeError
|
|
|
|
t.Message = fmt.Sprintf("status: %v, code: %v", t.info["status"], t.info["code"])
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) ReadJudgeReport(judgeFile string) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-01-06 17:31:00 +08:00
|
|
|
j, err := file.Read(judgeFile)
|
2022-10-22 17:38:39 +08:00
|
|
|
if err != nil {
|
|
|
|
t.Verdict = VerdictSystemError
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Message = "cannot read judge report"
|
2022-10-22 17:38:39 +08:00
|
|
|
} else {
|
|
|
|
t.judgeText = string(j)
|
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) DecodeJudgeReport() *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
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
|
2024-02-15 12:53:57 +08:00
|
|
|
t.Message = "cannot parse judge report"
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (t *TaskStatus) CheckJudgeReport(pts *map[int]int32) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
func (s *service) CheckResults(meta *JudgeMeta, prResults ProblemRunResults) (*JudgeStatus, int32) {
|
2022-10-22 17:38:39 +08:00
|
|
|
// CE will be processed in phase compile
|
|
|
|
|
|
|
|
pts := map[int]int32{}
|
2024-01-29 21:15:39 +08:00
|
|
|
for _, task := range meta.Cfg.All.Tasks {
|
2022-10-22 17:38:39 +08:00
|
|
|
pts[task.Id] = task.Points
|
|
|
|
}
|
|
|
|
|
|
|
|
var results []TaskStatus
|
2024-01-29 21:15:39 +08:00
|
|
|
dir := filepath.Join(UserDir, meta.Run.User)
|
2022-10-22 17:38:39 +08:00
|
|
|
var sum int32 = 0
|
|
|
|
|
2024-01-29 21:15:39 +08:00
|
|
|
for i := 1; i <= len(meta.Cfg.All.Tasks); i++ {
|
2022-10-22 17:38:39 +08:00
|
|
|
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))
|
|
|
|
|
2024-02-15 12:53:57 +08:00
|
|
|
result.ReadSandboxInfo(info).
|
|
|
|
ExtractSandboxInfo().
|
|
|
|
MergeContainerInfo(&prResults[i].Status).
|
|
|
|
CheckTime(meta.Cfg.Lang).
|
|
|
|
CheckMemory(meta.Cfg.Lang).
|
|
|
|
CheckExitCode().
|
|
|
|
ReadJudgeReport(judge).
|
|
|
|
DecodeJudgeReport().
|
|
|
|
CheckJudgeReport(&pts)
|
2022-10-22 17:38:39 +08:00
|
|
|
|
|
|
|
sum += result.Points
|
2022-10-23 17:29:35 +08:00
|
|
|
results = append(results, result)
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
return &JudgeStatus{Message: "", Tasks: results}, sum
|
2022-10-22 17:38:39 +08:00
|
|
|
}
|