mirror of
https://github.com/criyle/go-judge.git
synced 2025-11-04 14:50:02 +08:00
234 lines
5.4 KiB
Go
234 lines
5.4 KiB
Go
package runner
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/criyle/go-judge/file"
|
|
"github.com/criyle/go-judge/file/localfile"
|
|
"github.com/criyle/go-judge/file/memfile"
|
|
"github.com/criyle/go-judge/language"
|
|
"github.com/criyle/go-judge/pkg/diff"
|
|
"github.com/criyle/go-judge/pkg/runner"
|
|
"github.com/criyle/go-judge/types"
|
|
)
|
|
|
|
const maxOutput = 4 << 20 // 4M
|
|
|
|
func (r *Runner) compile(done <-chan struct{}, task *types.CompileTask) *types.RunTaskResult {
|
|
param := r.Language.Get(task.Language, language.TypeCompile)
|
|
|
|
compileErr := func(err string) *types.RunTaskResult {
|
|
return &types.RunTaskResult{
|
|
Status: types.RunTaskFailed,
|
|
Compile: &types.CompileResult{
|
|
Error: err,
|
|
},
|
|
}
|
|
}
|
|
|
|
// source code
|
|
source, err := task.Code.Content()
|
|
if err != nil {
|
|
return compileErr("File Error")
|
|
}
|
|
|
|
// copyin files
|
|
copyIn := make(map[string]file.File)
|
|
copyIn[param.SourceFileName] = memfile.New("source", source)
|
|
for _, f := range task.ExtraFiles {
|
|
copyIn[f.Name()] = f
|
|
}
|
|
|
|
// copyout files: If compile read compiled files
|
|
var copyOut []string
|
|
copyOut = param.CompiledFileNames
|
|
|
|
// compile message (stdout & stderr)
|
|
const msgFileName = "msg"
|
|
msgCollector := runner.PipeCollector{Name: msgFileName, SizeLimit: maxOutput}
|
|
devNull := localfile.New("null", os.DevNull)
|
|
|
|
// time limit
|
|
wait := &waiter{timeLimit: time.Duration(param.TimeLimit) * time.Millisecond}
|
|
|
|
// build run specs
|
|
c := &runner.Cmd{
|
|
Args: param.Args,
|
|
Env: param.Env,
|
|
Files: []interface{}{devNull, msgCollector, msgCollector},
|
|
MemoryLimit: param.MemoryLimit << 10,
|
|
PidLimit: param.ProcLimit,
|
|
CopyIn: copyIn,
|
|
CopyOut: copyOut,
|
|
Waiter: wait.Wait,
|
|
}
|
|
|
|
// run
|
|
rn := &runner.Runner{
|
|
CGBuilder: r.CgroupBuilder,
|
|
MasterPool: r.pool,
|
|
Cmds: []*runner.Cmd{c},
|
|
}
|
|
|
|
rt, err := rn.Run()
|
|
if err != nil {
|
|
return compileErr(err.Error())
|
|
}
|
|
r0 := rt[0]
|
|
|
|
// get compile message
|
|
compileMsg, err := getFile(r0.Files, msgFileName)
|
|
if err != nil {
|
|
return compileErr("FileError" + err.Error())
|
|
}
|
|
|
|
// compile copyout
|
|
var exec []file.File
|
|
for _, n := range param.CompiledFileNames {
|
|
exec = append(exec, r0.Files[n])
|
|
}
|
|
|
|
// return result
|
|
return &types.RunTaskResult{
|
|
Status: types.RunTaskSucceeded,
|
|
Compile: &types.CompileResult{
|
|
Exec: &types.CompiledExec{
|
|
Language: task.Language,
|
|
Exec: exec,
|
|
},
|
|
Error: string(compileMsg),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *Runner) exec(done <-chan struct{}, task *types.ExecTask) *types.RunTaskResult {
|
|
param := r.Language.Get(task.Exec.Language, language.TypeExec)
|
|
|
|
execErr := func(err string) *types.RunTaskResult {
|
|
return &types.RunTaskResult{
|
|
Status: types.RunTaskFailed,
|
|
Exec: &types.ExecResult{
|
|
Error: err,
|
|
},
|
|
}
|
|
}
|
|
|
|
// init input / output / error files
|
|
const stdout = "stdout"
|
|
const stderr = "stderr"
|
|
outputCollector := runner.PipeCollector{Name: stdout, SizeLimit: maxOutput}
|
|
errorCollector := runner.PipeCollector{Name: stderr, SizeLimit: maxOutput}
|
|
|
|
// calculate time limits
|
|
timeLimit := time.Duration(param.TimeLimit) * time.Millisecond
|
|
if task.TimeLimit > 0 {
|
|
timeLimit = time.Duration(task.TimeLimit) * time.Millisecond
|
|
}
|
|
wait := &waiter{timeLimit: timeLimit}
|
|
|
|
// calculate memory limits
|
|
memoryLimit := param.MemoryLimit << 10
|
|
if task.MemoryLimit > 0 {
|
|
memoryLimit = task.MemoryLimit << 10
|
|
}
|
|
|
|
// copyin files
|
|
copyIn := make(map[string]file.File)
|
|
for _, f := range task.Exec.Exec {
|
|
copyIn[f.Name()] = f
|
|
}
|
|
|
|
// copyout files
|
|
var copyOut []string
|
|
|
|
// build run specs
|
|
c := &runner.Cmd{
|
|
Args: param.Args,
|
|
Env: param.Env,
|
|
Files: []interface{}{task.InputFile, outputCollector, errorCollector},
|
|
MemoryLimit: memoryLimit,
|
|
PidLimit: param.ProcLimit,
|
|
CopyIn: copyIn,
|
|
CopyOut: copyOut,
|
|
Waiter: wait.Wait,
|
|
}
|
|
|
|
// run
|
|
rn := &runner.Runner{
|
|
CGBuilder: r.CgroupBuilder,
|
|
MasterPool: r.pool,
|
|
Cmds: []*runner.Cmd{c},
|
|
}
|
|
|
|
rt, err := rn.Run()
|
|
if err != nil {
|
|
return execErr("JGF" + err.Error())
|
|
}
|
|
r0 := rt[0]
|
|
|
|
// get result files
|
|
inputContent, err := task.InputFile.Content()
|
|
if err != nil {
|
|
return execErr("FileError" + err.Error())
|
|
}
|
|
userOutput, err := getFile(r0.Files, stdout)
|
|
if err != nil {
|
|
return execErr("FileError" + err.Error())
|
|
}
|
|
userError, err := getFile(r0.Files, stderr)
|
|
if err != nil {
|
|
return execErr("FileError" + err.Error())
|
|
}
|
|
|
|
// compare result with answer (no spj now)
|
|
var (
|
|
status = r0.Status.String()
|
|
spjOutput []byte
|
|
scoreRate float64 = 1
|
|
)
|
|
ans, err := task.AnswerFile.Content()
|
|
if err != nil {
|
|
return execErr("FileError" + err.Error())
|
|
}
|
|
if err := diff.Compare(bytes.NewReader(ans), bytes.NewReader(userOutput)); err != nil {
|
|
spjOutput = []byte(err.Error())
|
|
scoreRate = 0
|
|
status = "WA"
|
|
}
|
|
|
|
// return result
|
|
return &types.RunTaskResult{
|
|
Status: types.RunTaskSucceeded,
|
|
Exec: &types.ExecResult{
|
|
ScoringRate: scoreRate,
|
|
Error: status,
|
|
Time: uint64(r0.Time / time.Millisecond),
|
|
Memory: r0.Memory >> 10,
|
|
Input: inputContent,
|
|
UserOutput: userOutput,
|
|
UserError: userError,
|
|
SPJOutput: spjOutput,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskResult {
|
|
switch task.Type {
|
|
case types.Compile:
|
|
return r.compile(done, task.Compile)
|
|
|
|
default:
|
|
return r.exec(done, task.Exec)
|
|
}
|
|
}
|
|
|
|
func getFile(files map[string]file.File, name string) ([]byte, error) {
|
|
if f, ok := files[name]; ok {
|
|
return f.Content()
|
|
}
|
|
return nil, fmt.Errorf("file %s not exists", name)
|
|
}
|