diff --git a/frontend/html/admin/file_inputs.html b/frontend/html/admin/file_inputs.html index ffc70ee..021828f 100644 --- a/frontend/html/admin/file_inputs.html +++ b/frontend/html/admin/file_inputs.html @@ -84,6 +84,9 @@ py3, pas. +
  • + .stages: The file contains a list of arguments passed to the binary. .stages is required for a chained task type. Refer to here for further information about preparing a problem of this type. +
  • Any other files (e.g headers, provided libraries, ...) will be available at the current working directory when compiling contestant's solutions. diff --git a/worker/run.go b/worker/run.go index 7491e62..b13f545 100644 --- a/worker/run.go +++ b/worker/run.go @@ -113,6 +113,58 @@ func (r *RunContext) CompareInput(submissionOutput []byte) (input *SandboxInput, }, true, nil } +func RunSingleCommand(sandbox Sandbox, r *RunContext, source []byte) (output *SandboxOutput, err error) { + // First, use the sandbox to run the submission itself. + input, err := r.RunInput(source) + if err != nil { + return nil, err + } + output, err = sandbox.Run(input) + if err != nil { + return nil, errors.WithStack(err) + } + return output, nil +} + +func RunMutipleCommands(sandbox Sandbox, r *RunContext, source []byte, stages []string) (output *SandboxOutput, err error) { + command, args, err := RunCommand(r.Sub.Language) + if err != nil { + return nil, err + } + + input := r.Test.Input + for i, stage := range stages { + // somehow Go includes EOF when splitting a string file line by line + if stage == "" && i == len(stages)-1 { + continue + } + stageArgs := strings.Split(stage, " ") + + sandboxInput := &SandboxInput{ + Command: command, + Args: append(stageArgs, args...), + Files: nil, + TimeLimit: r.TimeLimit(), + MemoryLimit: r.MemoryLimit(), + + CompiledSource: source, + Input: input, + } + + output, err = sandbox.Run(sandboxInput) + if err != nil { + return nil, err + } + // stop if the current run fails + if !output.Success { + break + } + // Next input in the chain will be the standard output of the previous command run + input = output.Stdout + } + return output, nil +} + // Run runs a RunContext. func Run(sandbox Sandbox, r *RunContext) error { compiled, source := r.CompiledSource() @@ -128,17 +180,26 @@ func Run(sandbox Sandbox, r *RunContext) error { log.Printf("[WORKER] Running submission %v on [test `%v`, group `%v`]\n", r.Sub.ID, r.Test.Name, r.TestGroup.Name) - // First, use the sandbox to run the submission itself. - input, err := r.RunInput(source) - if err != nil { + var output *SandboxOutput + file, err := models.GetFileWithName(r.DB, r.Problem.ID, ".stages") + if errors.Is(err, sql.ErrNoRows) { + // Problem type is not Chained Type, run a single command + output, err = RunSingleCommand(sandbox, r, source) + if err != nil { + return err + } + } else if err != nil { return err + } else { + // Problem Type is Chained Type, we need to run mutiple commands with arguments from .stages (file) + stages := strings.Split(string(file.Content), "\n") + output, err = RunMutipleCommands(sandbox, r, source, stages) + if err != nil { + return err + } } - output, err := sandbox.Run(input) - if err != nil { - return errors.WithStack(err) - } - result := parseSandboxOutput(output, r) + result := parseSandboxOutput(output, r) if !output.Success { result.Verdict = "Runtime Error" if output.ErrorMessage != "" {