1
1
package commands
2
2
3
3
import (
4
+ "bufio"
5
+ "bytes"
4
6
"errors"
5
7
"fmt"
8
+ "github.com/chermehdi/egor/config"
9
+ "io/ioutil"
10
+ "os"
11
+ "os/exec"
12
+ "path"
13
+ "path/filepath"
6
14
"strings"
7
15
8
16
"github.com/urfave/cli/v2"
9
17
)
10
18
19
+ var (
20
+ AC int8 = 0
21
+ SK int8 = 1
22
+ RE int8 = 2
23
+ WA int8 = 3
24
+ )
25
+
11
26
// Checks the output of a given testcase against it's expected output
12
27
type Checker interface {
13
28
// Execute the check (got, expected) and returns
@@ -31,12 +46,34 @@ func (c *DiffChecker) Check(got, expected string) error {
31
46
type CaseDescription struct {
32
47
InputFile string
33
48
OutputFile string
49
+ WorkFile string
34
50
CustomCase bool
35
51
}
36
52
53
+ func getWorkFile (fileName string ) string {
54
+ fileNameParts := strings .Split (fileName , "." )
55
+ fileNameNoExtension := fileNameParts [0 ]
56
+ return fmt .Sprintf ("%s-ex.out" , fileNameNoExtension )
57
+ }
58
+
59
+ // Creates a new CaseDescription from a pair of input and output IoFiles
60
+ func NewCaseDescription (input , output config.IoFile ) * CaseDescription {
61
+ base , file := filepath .Split (input .Path )
62
+ workFilePath := path .Join (base , getWorkFile (file ))
63
+ return & CaseDescription {
64
+ InputFile : input .Path ,
65
+ OutputFile : output .Path ,
66
+ WorkFile : workFilePath ,
67
+ CustomCase : input .Custom ,
68
+ }
69
+ }
70
+
37
71
// Report the execution status for a given testcase.
38
- // Type also stores the compiler output, and the checker response
72
+ // Type stores also checker response
39
73
type CaseStatus struct {
74
+ Status int8
75
+ CheckerError error
76
+ Stderr string
40
77
}
41
78
42
79
// Implementation must be able to prepare the working environement to compile and execute testscases,
@@ -55,10 +92,210 @@ type Judge interface {
55
92
Cleanup () error
56
93
}
57
94
58
- func RunAction (context * cli.Context ) error {
95
+ // This represents the result of running the testcases of a given task
96
+ type JudgeReport interface {
97
+ Add (status CaseStatus )
98
+
99
+ Display () string
100
+ }
101
+
102
+ type ConsoleJudgeReport struct {
103
+ Stats []CaseStatus
104
+ }
105
+
106
+ func (c * ConsoleJudgeReport ) Add (status CaseStatus ) {
107
+ c .Stats = append (c .Stats , status )
108
+ }
109
+
110
+ func (c * ConsoleJudgeReport ) Display () string {
111
+ panic ("implement me" )
112
+ }
113
+
114
+ func newJudgeReport () JudgeReport {
115
+ return & ConsoleJudgeReport {Stats : []CaseStatus {}}
116
+ }
117
+
118
+ //
119
+ // Java judge
120
+ //
121
+ type JavaJudge struct {
122
+ Meta config.EgorMeta
123
+ CurrentWorkDir string
124
+ Checker Checker
125
+ }
126
+
127
+ func (judge * JavaJudge ) Setup () error {
128
+ currentDir , err := os .Getwd ()
129
+ if err != nil {
130
+ return err
131
+ }
132
+ workDirPath := path .Join (currentDir , "work" )
133
+ if err = os .Mkdir (workDirPath , 777 ); err != nil {
134
+ return err
135
+ }
136
+ //TODO(chermehdi): make the executables path configurable #14
137
+
138
+ // Compilation for Java
139
+ cmd := exec .Command ("javac" , judge .Meta .TaskFile , "-d" , workDirPath )
140
+ if err = cmd .Run (); err != nil {
141
+ return err
142
+ }
143
+ judge .CurrentWorkDir = workDirPath
144
+ return nil
145
+ }
146
+
147
+ func (judge * JavaJudge ) RunTestCase (desc CaseDescription ) CaseStatus {
148
+ // We suppose that all java executables will be called Main
149
+ execFilePath := path .Join (judge .CurrentWorkDir , "Main" )
150
+ cmd := exec .Command ("java" , execFilePath )
151
+ cmd .Dir = judge .CurrentWorkDir
152
+ inputFile , err := os .Open (desc .InputFile )
153
+ if err != nil {
154
+ return CaseStatus {
155
+ Status : RE ,
156
+ CheckerError : nil ,
157
+ }
158
+ }
159
+ defer inputFile .Close ()
160
+
161
+ outputFile , err := os .OpenFile (desc .WorkFile , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0777 )
162
+ if err != nil {
163
+ return CaseStatus {
164
+ Status : RE ,
165
+ CheckerError : nil ,
166
+ }
167
+ }
168
+ defer outputFile .Close ()
169
+
170
+ var stderrBuffer bytes.Buffer
171
+ cmd .Stdin = inputFile
172
+ cmd .Stdout = bufio .NewWriter (outputFile )
173
+ cmd .Stderr = & stderrBuffer
174
+ if err = cmd .Run (); err != nil {
175
+ return CaseStatus {
176
+ Status : RE ,
177
+ CheckerError : nil ,
178
+ Stderr : stderrBuffer .String (),
179
+ }
180
+
181
+ }
182
+
183
+ expectedOutput , err := ioutil .ReadFile (desc .OutputFile )
184
+ if err != nil {
185
+ return CaseStatus {
186
+ Status : RE ,
187
+ CheckerError : nil ,
188
+ }
189
+ }
190
+ output , err := ioutil .ReadFile (desc .WorkFile )
191
+ if err != nil {
192
+ return CaseStatus {
193
+ Status : RE ,
194
+ CheckerError : nil ,
195
+ }
196
+ }
197
+ err = judge .Checker .Check (string (output ), string (expectedOutput ))
198
+ if err != nil {
199
+ return CaseStatus {
200
+ Status : WA ,
201
+ CheckerError : err ,
202
+ Stderr : stderrBuffer .String (),
203
+ }
204
+ }
205
+ return CaseStatus {
206
+ Status : AC ,
207
+ CheckerError : err ,
208
+ Stderr : stderrBuffer .String (),
209
+ }
210
+ }
211
+
212
+ func (judge * JavaJudge ) Cleanup () error {
213
+ return os .RemoveAll (judge .CurrentWorkDir )
214
+ }
215
+
216
+ //
217
+ // C / Cpp Judge
218
+ //
219
+ type CppJudge struct {
220
+ Meta config.EgorMeta
221
+ }
222
+
223
+ func (judge * CppJudge ) Setup () error {
224
+ return nil
225
+ }
226
+
227
+ func (judge * CppJudge ) RunTestCase (description CaseDescription ) CaseStatus {
228
+ return CaseStatus {}
229
+ }
230
+ func (judge * CppJudge ) Cleanup () error {
59
231
return nil
60
232
}
61
233
234
+ //
235
+ // Python Judge
236
+ //
237
+ type PythonJudge struct {
238
+ Meta config.EgorMeta
239
+ }
240
+
241
+ func (judge * PythonJudge ) Setup () error {
242
+ panic ("implement me" )
243
+ }
244
+
245
+ func (judge * PythonJudge ) RunTestCase (CaseDescription ) CaseStatus {
246
+ panic ("implement me" )
247
+ }
248
+
249
+ func (judge * PythonJudge ) Cleanup () error {
250
+ panic ("implement me" )
251
+ }
252
+
253
+ // Creates and returns a Judge implementation corresponding to the given language
254
+ func NewJudgeFor (meta config.EgorMeta ) (Judge , error ) {
255
+ switch meta .TaskLang {
256
+ case "java" :
257
+ return & JavaJudge {Meta : meta }, nil
258
+ case "cpp" :
259
+ case "c" :
260
+ return & CppJudge {Meta : meta }, nil
261
+ case "python" :
262
+ return & PythonJudge {Meta : meta }, nil
263
+ }
264
+ return nil , errors .New (fmt .Sprintf ("Cannot find judge for the given lang %s" , meta .TaskLang ))
265
+ }
266
+
267
+ func RunAction (_ * cli.Context ) error {
268
+ configuration , err := config .LoadDefaultConfiguration ()
269
+ if err != nil {
270
+ return err
271
+ }
272
+ cwd , err := os .Getwd ()
273
+ if err != nil {
274
+ return err
275
+ }
276
+ egorMetaFile := path .Join (cwd , configuration .ConfigFileName )
277
+ egorMeta , err := config .LoadMetaFromPath (egorMetaFile )
278
+ if err != nil {
279
+ return err
280
+ }
281
+
282
+ judge , err := NewJudgeFor (egorMeta )
283
+ if err != nil {
284
+ return err
285
+ }
286
+ if err := judge .Setup (); err != nil {
287
+ return err
288
+ }
289
+ defer judge .Cleanup ()
290
+ report := newJudgeReport ()
291
+ for i , input := range egorMeta .Inputs {
292
+ output := egorMeta .Outputs [i ]
293
+ caseDescription := NewCaseDescription (input , output )
294
+ status := judge .RunTestCase (* caseDescription )
295
+ report .Add (status )
296
+ }
297
+ }
298
+
62
299
var TestCommand = cli.Command {
63
300
Name : "test" ,
64
301
Aliases : []string {"r" },
0 commit comments