Skip to content

Commit 5f259d0

Browse files
authored
Merge pull request #40 from chermehdi/feature/timed_execution
Feature/timed execution
2 parents d02f9ec + 99597e1 commit 5f259d0

File tree

3 files changed

+92
-21
lines changed

3 files changed

+92
-21
lines changed

commands/run.go

+62-8
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7-
"github.com/chermehdi/egor/config"
8-
"github.com/chermehdi/skimo/skimo"
9-
"github.com/fatih/color"
10-
"github.com/jedib0t/go-pretty/table"
117
"io/ioutil"
128
"os"
139
"os/exec"
1410
"path"
1511
"path/filepath"
1612
"strings"
13+
"time"
14+
15+
"github.com/chermehdi/egor/config"
16+
"github.com/chermehdi/skimo/skimo"
17+
"github.com/fatih/color"
18+
"github.com/jedib0t/go-pretty/table"
1719

1820
"github.com/urfave/cli/v2"
1921
)
@@ -23,17 +25,27 @@ var (
2325
SK int8 = 1
2426
RE int8 = 2
2527
WA int8 = 3
28+
TL int8 = 4
29+
)
30+
31+
var (
32+
OK int8 = 0 // OK execution status
33+
TO int8 = 1 // Timed out status
2634
)
2735

2836
var (
2937
green = color.New(color.FgGreen).SprintfFunc()
3038
red = color.New(color.FgRed).SprintfFunc()
3139
magenta = color.New(color.FgMagenta).SprintfFunc()
3240
yellow = color.New(color.FgYellow).SprintfFunc()
41+
blue = color.New(color.FgBlue).SprintfFunc()
3342
)
3443

3544
const WorkDir = "work"
3645

46+
// Time out delta in miliseconds
47+
const TimeOutDelta float64 = 25.0
48+
3749
// Checks the output of a given testcase against it's expected output
3850
type Checker interface {
3951
// Execute the check (got, expected) and returns
@@ -59,6 +71,7 @@ type CaseDescription struct {
5971
OutputFile string
6072
WorkFile string
6173
CustomCase bool
74+
TimeLimit float64
6275
}
6376

6477
func getWorkFile(fileName string) string {
@@ -68,14 +81,15 @@ func getWorkFile(fileName string) string {
6881
}
6982

7083
// Creates a new CaseDescription from a pair of input and output IoFiles
71-
func NewCaseDescription(input, output config.IoFile) *CaseDescription {
84+
func NewCaseDescription(input, output config.IoFile, timeLimit float64) *CaseDescription {
7285
base, file := filepath.Split(input.Path)
7386
workFilePath := path.Join(base, getWorkFile(file))
7487
return &CaseDescription{
7588
InputFile: input.Path,
7689
OutputFile: output.Path,
7790
WorkFile: workFilePath,
7891
CustomCase: input.Custom,
92+
TimeLimit: timeLimit,
7993
}
8094
}
8195

@@ -85,6 +99,7 @@ type CaseStatus struct {
8599
Status int8
86100
CheckerError error
87101
Stderr string
102+
Duration time.Duration
88103
}
89104

90105
// Implementation must be able to prepare the working environment to compile and execute testscases,
@@ -144,6 +159,8 @@ func getDisplayStatus(status int8) string {
144159
return yellow("SK")
145160
case WA:
146161
return red("WA")
162+
case TL:
163+
return blue("TL")
147164
}
148165
return "Unknown"
149166
}
@@ -158,7 +175,7 @@ func getStderrDisplay(stderr string) string {
158175
func (c *ConsoleJudgeReport) Display() {
159176
t := table.NewWriter()
160177
t.SetOutputMirror(os.Stdout)
161-
t.AppendHeader(table.Row{"#", "Test Name", "Status", "Custom", "Additional infos", "Stderr"})
178+
t.AppendHeader(table.Row{"#", "Test Name", "Status", "Custom", "Additional infos", "Stderr", "Execution Time"})
162179
for i, stat := range c.Stats {
163180
output := "None"
164181
if stat.CheckerError != nil {
@@ -171,6 +188,7 @@ func (c *ConsoleJudgeReport) Display() {
171188
c.Descs[i].CustomCase,
172189
output,
173190
getStderrDisplay(stat.Stderr),
191+
stat.Duration,
174192
})
175193
}
176194
t.SetStyle(table.StyleLight)
@@ -181,6 +199,26 @@ func newJudgeReport() JudgeReport {
181199
return &ConsoleJudgeReport{Stats: []CaseStatus{}}
182200
}
183201

202+
// Utility function to execute a given command and insure to stop it after a timeOut (in miliseconds).
203+
// The function returns the status of the execution, the duration of the exeuction, and an error (if any).
204+
func timedExecution(cmd *exec.Cmd, timeOut float64) (int8, time.Duration, error) {
205+
cmd.Start()
206+
start := time.Now()
207+
done := make(chan error)
208+
go func() { done <- cmd.Wait() }()
209+
210+
timeout := time.After(time.Duration(timeOut) * time.Millisecond)
211+
select {
212+
case <-timeout:
213+
elapsed := time.Since(start)
214+
cmd.Process.Kill()
215+
return TO, elapsed, nil
216+
case err := <-done:
217+
elapsed := time.Since(start)
218+
return OK, elapsed, err
219+
}
220+
}
221+
184222
// Utility function to execute the given command that is associated with the given judge
185223
// the method returns the case status and the error (if any)
186224
func execute(judge Judge, desc CaseDescription, command string, args ...string) (CaseStatus, error) {
@@ -208,11 +246,23 @@ func execute(judge Judge, desc CaseDescription, command string, args ...string)
208246
cmd.Stdin = inputFile
209247
cmd.Stdout = outputFile
210248
cmd.Stderr = &stderrBuffer
211-
if err = cmd.Run(); err != nil {
249+
250+
status, duration, err := timedExecution(cmd, desc.TimeLimit+TimeOutDelta)
251+
if status == TO {
252+
return CaseStatus{
253+
Status: TL,
254+
CheckerError: nil,
255+
Stderr: stderrBuffer.String(),
256+
Duration: duration,
257+
}, nil
258+
}
259+
260+
if err != nil {
212261
return CaseStatus{
213262
Status: RE,
214263
CheckerError: nil,
215264
Stderr: stderrBuffer.String(),
265+
Duration: duration,
216266
}, err
217267
}
218268

@@ -221,6 +271,7 @@ func execute(judge Judge, desc CaseDescription, command string, args ...string)
221271
return CaseStatus{
222272
Status: RE,
223273
CheckerError: nil,
274+
Duration: duration,
224275
}, err
225276
}
226277
output, err := ioutil.ReadFile(desc.WorkFile)
@@ -229,6 +280,7 @@ func execute(judge Judge, desc CaseDescription, command string, args ...string)
229280
return CaseStatus{
230281
Status: RE,
231282
CheckerError: nil,
283+
Duration: duration,
232284
}, err
233285
}
234286
err = judge.Checker().Check(string(output), string(expectedOutput))
@@ -237,12 +289,14 @@ func execute(judge Judge, desc CaseDescription, command string, args ...string)
237289
Status: WA,
238290
CheckerError: err,
239291
Stderr: stderrBuffer.String(),
292+
Duration: duration,
240293
}, err
241294
}
242295
return CaseStatus{
243296
Status: AC,
244297
CheckerError: nil,
245298
Stderr: stderrBuffer.String(),
299+
Duration: duration,
246300
}, err
247301
}
248302

@@ -459,7 +513,7 @@ func RunAction(_ *cli.Context) error {
459513

460514
for i, input := range egorMeta.Inputs {
461515
output := egorMeta.Outputs[i]
462-
caseDescription := NewCaseDescription(input, output)
516+
caseDescription := NewCaseDescription(input, output, egorMeta.TimeLimit)
463517
status := judge.RunTestCase(*caseDescription)
464518
report.Add(status, *caseDescription)
465519
}

commands/run_test.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
package commands
22

3-
import "testing"
3+
import (
4+
"os/exec"
5+
"testing"
46

5-
func TestNothing(t *testing.T) {
6-
// NO-OP
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestTimedOutExecution(t *testing.T) {
11+
cmd := exec.Command("sleep", "1.1")
12+
status, _, err := timedExecution(cmd, 1000+TimeOutDelta)
13+
assert.Equal(t, err, nil)
14+
assert.Equal(t, status, TO)
15+
}
16+
17+
func TestTimedExecution(t *testing.T) {
18+
cmd := exec.Command("sleep", "1")
19+
status, _, err := timedExecution(cmd, 1000+TimeOutDelta)
20+
assert.Equal(t, err, nil)
21+
assert.Equal(t, status, OK)
722
}

config/meta.go

+12-10
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ func (ioFile *IoFile) GetId() int {
4040
// so an update to it (either from the outside, or by invoking egor commands) can change
4141
// the behavior of execution of the egor cli.
4242
type EgorMeta struct {
43-
TaskName string
44-
TaskLang string
45-
Inputs []IoFile
46-
Outputs []IoFile
47-
TaskFile string
43+
TaskName string
44+
TaskLang string
45+
Inputs []IoFile
46+
Outputs []IoFile
47+
TaskFile string
48+
TimeLimit float64
4849
}
4950

5051
// Resolves the task file given the default language.
@@ -75,11 +76,12 @@ func NewEgorMeta(task Task, config Config) EgorMeta {
7576
panic(err)
7677
}
7778
return EgorMeta{
78-
TaskName: task.Name,
79-
TaskLang: config.Lang.Default,
80-
Inputs: inputs,
81-
Outputs: outputs,
82-
TaskFile: taskFile,
79+
TaskName: task.Name,
80+
TaskLang: config.Lang.Default,
81+
Inputs: inputs,
82+
Outputs: outputs,
83+
TaskFile: taskFile,
84+
TimeLimit: task.TimeLimit,
8385
}
8486
}
8587

0 commit comments

Comments
 (0)