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"`
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskStatus) getInfoText(infoFile string) *TaskStatus {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (t *TaskStatus) checkTime(cLang *ConfigLanguage) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
if t.info["real_time"].(float64) > float64(cLang.Runtime.Run.TimeLimit)+5 {
|
2022-10-22 17:38:39 +08:00
|
|
|
t.Verdict = VerdictTimeLimitExceeded
|
|
|
|
t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.info["real_time"], t.info["cpu_time"])
|
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (t *TaskStatus) checkMemory(cLang *ConfigLanguage) *TaskStatus {
|
2022-10-22 17:38:39 +08:00
|
|
|
if t.Verdict != VerdictAccepted {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
if t.info["memory"].(float64) > float64((cLang.Runtime.Run.MemoryLimit+1)*1024) {
|
2022-10-22 17:38:39 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-01-29 21:15:39 +08:00
|
|
|
func (s *service) CheckResults(meta *JudgeMeta) (*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))
|
|
|
|
|
|
|
|
result.getInfoText(info).
|
|
|
|
getInfo().
|
2024-01-29 21:15:39 +08:00
|
|
|
checkTime(meta.Cfg.Lang).
|
|
|
|
checkMemory(meta.Cfg.Lang).
|
2022-10-23 17:29:35 +08:00
|
|
|
checkExit().
|
2022-10-22 17:38:39 +08:00
|
|
|
getJudgeText(judge).
|
|
|
|
getJudge().
|
|
|
|
checkJudge(&pts)
|
|
|
|
|
|
|
|
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
|
|
|
}
|