Skip to content

Commit 9b64206

Browse files
committed
Add first version of batch testing
Supported features now: - Creating the batching structure as described in the bug. - Running the batch with the given parameters Still missing: - User should be able to write the brute solution with it's preferred langauge, not necessarly C++ - "rand.h" is still a dummy library for now and will be added as part of another bug.
1 parent e518842 commit 9b64206

File tree

8 files changed

+461
-96
lines changed

8 files changed

+461
-96
lines changed

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,47 @@ type TemplateContext struct {
124124
```
125125
You can access any of the model fields in the template, and make changes accordingly dependending on your preferences.
126126
- Running the command `egor config set config.templates.{lang} /path/to/template/file` will register the given template and will use it for the given language for future tasks.
127+
128+
## Batch testing
129+
130+
- This one is usefull if you are stuck on a problem, your solution gets `WA` and
131+
you can't figure out a testcase that will make it break.
132+
- If you know how to write a brute force solution to the problem, egor can help
133+
with running the that solution that you know gives the correct answer against
134+
your (optimal) solution, and figure out when will they differ.
135+
136+
### Steps to make a batch test
137+
- Create a batch configuration
138+
139+
```
140+
egor batch create --has-brute
141+
```
142+
143+
Running this will create 3 additional files:
144+
145+
- `gen.cpp`: which is the random input generator, this include a small random
146+
generation library `"rand.h"`, feel free to use it to generate testcases for
147+
the given problem.
148+
- `main_brute.cpp`: which will act as the brute force solution.
149+
- `rand.h`: the random generation library, contains a bunch of helper methods
150+
to generate random input, read the source and docs on public methods for
151+
additional info.
152+
153+
Once all the files are filled according to their purpose, you can start running
154+
your batch test using
155+
156+
```
157+
egor batch run --tests=10
158+
```
159+
160+
the parameter `--tests` tells egor how many times it should run your code,
161+
a `100` is a good start.
162+
163+
If you can't find a failing testcase after this, you can increase the
164+
`--tests` parameter, or change the generation strategy, (ex: try bigger
165+
/ smaller numbers), egor will give your program a random seed every time it runs
166+
the generator, so that it generates a new testcase with every run.
167+
127168
## Contribution
128169

129170
- Contribution to the project can be done in multiple ways, you can report bugs and issues or you can discuss new features that you like being added in future versions by creating a new issue

commands/batch.go

+208-37
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
11
package commands
22

33
import (
4-
"os"
5-
"path"
6-
template2 "text/template"
7-
4+
"errors"
5+
"fmt"
86
"github.com/chermehdi/egor/config"
97
"github.com/chermehdi/egor/templates"
8+
"github.com/chermehdi/egor/utils"
109
"github.com/fatih/color"
1110
"io/ioutil"
11+
"log"
12+
"math/rand"
13+
"os"
14+
"path"
15+
template2 "text/template"
1216

1317
"github.com/urfave/cli/v2"
1418
)
1519

16-
type Batcher struct {
17-
judge Judge
18-
}
19-
20-
func (b *Batcher) Setup() error {
21-
b.judge.Setup()
22-
return nil
23-
}
24-
25-
func (b *Batcher) Run() error {
26-
return nil
27-
}
28-
29-
func (b *Batcher) Cleanup() error {
30-
b.judge.Cleanup()
31-
return nil
32-
}
20+
const (
21+
RandName = "rand.h"
22+
BruteName = "main_brute.cpp"
23+
GenName = "gen.cpp"
24+
)
3325

26+
// CreateBatch will create the batch directory structure with the files needed
3427
func CreateBatch(context *cli.Context) error {
3528
configuration, err := config.LoadDefaultConfiguration()
3629
if err != nil {
@@ -51,7 +44,7 @@ func CreateBatch(context *cli.Context) error {
5144
return nil
5245
}
5346

54-
genPath := path.Join(cwd, "gen.cpp")
47+
genPath := path.Join(cwd, GenName)
5548
egorMeta.BatchFile = genPath
5649
// Create the generator file
5750

@@ -68,10 +61,17 @@ func CreateBatch(context *cli.Context) error {
6861
if err = compiledTemplate.Execute(genFile, configuration); err != nil {
6962
return err
7063
}
71-
randH := path.Join(cwd, "rand.h")
72-
// Create the rand.h file
73-
if err = ioutil.WriteFile(randH, []byte(templates.RandH), 0755); err != nil {
74-
return err
64+
if context.Bool("has-brute") {
65+
randH := path.Join(cwd, RandName)
66+
// Create the rand.h file
67+
if err = ioutil.WriteFile(randH, []byte(templates.RandH), 0755); err != nil {
68+
return err
69+
}
70+
71+
bruteH := path.Join(cwd, BruteName)
72+
if err = ioutil.WriteFile(bruteH, []byte(templates.BruteH), 0755); err != nil {
73+
return err
74+
}
7575
}
7676
// Update the metadata
7777
fileName := path.Join(cwd, configuration.ConfigFileName)
@@ -82,7 +82,7 @@ func CreateBatch(context *cli.Context) error {
8282
return nil
8383
}
8484

85-
func RunBatch(context *cli.Context) error {
85+
func runBatchInternal(context *cli.Context) error {
8686
configuration, err := config.LoadDefaultConfiguration()
8787
if err != nil {
8888
return err
@@ -96,27 +96,182 @@ func RunBatch(context *cli.Context) error {
9696
if err != nil {
9797
return err
9898
}
99+
if !egorMeta.HasBatch() {
100+
color.Red("The current task does not have a batch setting, did you forget to run egor batch create?")
101+
return nil
102+
}
103+
n := context.Int("tests")
104+
runner, found := utils.CreateRunner(egorMeta.TaskLang)
105+
// This used to run the generator
106+
if !found {
107+
color.Red("No task runner found for the task's default language, make sure to not change the egor metafile unless manually unless you know what you are doing!")
108+
return errors.New("Task runner not found!")
109+
}
110+
cppr, _ := utils.CreateRunner("cpp")
99111

100-
judge, err := NewJudgeFor(egorMeta, configuration, getChecker(context.String("checker")))
112+
// Compile the generator
113+
c := &utils.ExecutionContext{
114+
FileName: GenName,
115+
BinaryName: "gen",
116+
Input: nil,
117+
Dir: cwd,
118+
}
119+
120+
log.Println("Compiling the generator...")
121+
122+
res, err := cppr.Compile(c)
101123
if err != nil {
124+
if res.Stderr.String() != "" {
125+
color.Red(res.Stderr.String())
126+
}
102127
return err
103128
}
104-
if err := judge.Setup(); err != nil {
129+
130+
log.Println("Finished compiling the generator")
131+
132+
log.Println("Compiling the solution ...")
133+
binName := getBinaryName(egorMeta.TaskLang)
134+
// Compile the solution
135+
c1 := &utils.ExecutionContext{
136+
FileName: egorMeta.TaskFile,
137+
BinaryName: binName,
138+
Input: nil,
139+
Dir: cwd,
140+
}
141+
142+
res, err = runner.Compile(c1)
143+
if err != nil {
144+
if res.Stderr.String() != "" {
145+
color.Red(res.Stderr.String())
146+
}
147+
return err
148+
}
149+
150+
log.Println("Finished compiling the solution")
151+
152+
log.Println("Finished compiling the brute force solution")
153+
// Compile the brute force solution
154+
c2 := &utils.ExecutionContext{
155+
FileName: BruteName,
156+
BinaryName: "brute",
157+
Input: nil,
158+
Dir: cwd,
159+
}
160+
161+
res, err = cppr.Compile(c2)
162+
if err != nil {
163+
if res.Stderr.String() != "" {
164+
color.Red(res.Stderr.String())
165+
}
105166
return err
106167
}
107-
defer judge.Cleanup()
108-
report := newJudgeReport()
109168

110-
for i, input := range egorMeta.Inputs {
111-
output := egorMeta.Outputs[i]
112-
caseDescription := NewCaseDescription(input, output, egorMeta.TimeLimit)
113-
status := judge.RunTestCase(*caseDescription)
114-
report.Add(status, *caseDescription)
169+
log.Println("Finished compiling the brute force solution")
170+
171+
checker := &TokenChecker{}
172+
173+
for i := 0; i < n; i = i + 1 {
174+
color.Green(fmt.Sprintf("Running test %d", i+1))
175+
176+
log.Println("Running the generator.. ")
177+
// Args should contain the random seed to give generator as argument
178+
c.Args = fmt.Sprintf("%d", rand.Intn(int(1<<30)))
179+
180+
// Get the random input
181+
res, err = cppr.Run(c)
182+
if err != nil {
183+
color.Red("Could not run the generator ")
184+
if res.Stderr.String() != "" {
185+
color.Red(res.Stderr.String())
186+
}
187+
return err
188+
}
189+
log.Println("Finished running the generator")
190+
log.Printf("Generator output: \n%s\n", res.Stdout.String())
191+
192+
log.Println("Running the main solution..")
193+
194+
// making two input copies:
195+
// - one for the brute solution consumer
196+
// - second only used in case of a found diff to print the output to the
197+
// input to the user
198+
inC1 := res.Stdout
199+
inC2 := res.Stdout
200+
201+
// Run the main solution
202+
// Feed the output of the generator as the main solution's input.
203+
c1.Input = &res.Stdout
204+
main, err := runner.Run(c1)
205+
if err != nil {
206+
color.Red("Could not run the solution")
207+
if main.Stderr.String() != "" {
208+
color.Red(main.Stderr.String())
209+
}
210+
return err
211+
}
212+
213+
log.Println("Finished running main the solution")
214+
215+
// Run the main solution
216+
// Feed the output of the generator as the brute solution's input.
217+
log.Println("Running the brute solution ...")
218+
219+
c2.Input = &inC1
220+
brute, err := cppr.Run(c2)
221+
if err != nil {
222+
color.Red("Could not run the brute solution")
223+
if brute.Stderr.String() != "" {
224+
color.Red(brute.Stderr.String())
225+
}
226+
return err
227+
}
228+
229+
if err := checker.Check(brute.Stdout.String(), main.Stdout.String()); err != nil {
230+
color.Red("Found diff: ")
231+
color.Red("Input: ")
232+
fmt.Println(inC2.String())
233+
color.Red("Expected: ")
234+
fmt.Println(brute.Stdout.String())
235+
color.Red("Got: ")
236+
fmt.Println(main.Stdout.String())
237+
color.Red(err.Error())
238+
return err
239+
}
115240
}
116-
report.Display()
241+
242+
color.Green("All green, not diff found - Your solution is probably correct!")
117243
return nil
118244
}
119245

246+
// Compute the binary name according to the language.
247+
func getBinaryName(lang string) string {
248+
switch lang {
249+
case "cpp":
250+
return "sol"
251+
case "java":
252+
return "Main"
253+
case "python":
254+
return "main.py"
255+
}
256+
// should not get here.
257+
return ""
258+
}
259+
260+
// RunBatch will delegate to the internal function to do the actual batch
261+
// running and it will handle clean up afterwards (delete residual binary
262+
// files).
263+
func RunBatch(context *cli.Context) error {
264+
err := runBatchInternal(context)
265+
cwd, err := os.Getwd()
266+
if err != nil {
267+
return err
268+
}
269+
os.Remove(path.Join(cwd, "sol"))
270+
os.Remove(path.Join(cwd, "brute"))
271+
os.Remove(path.Join(cwd, "gen"))
272+
return err
273+
}
274+
120275
var BatchCommand = cli.Command{
121276
Name: "batch",
122277
Aliases: []string{"b"},
@@ -128,12 +283,28 @@ var BatchCommand = cli.Command{
128283
Aliases: []string{"c"},
129284
Usage: "Create the template for the batch test",
130285
Action: CreateBatch,
286+
Flags: []cli.Flag{
287+
&cli.BoolFlag{
288+
Name: "has-brute",
289+
Usage: "Does this solution have a brute force solution",
290+
Aliases: []string{"b"},
291+
Value: true,
292+
},
293+
},
131294
},
132295
{
133296
Name: "run",
134297
Aliases: []string{"r"},
135298
Usage: "Run the batch test",
136299
Action: RunBatch,
300+
Flags: []cli.Flag{
301+
&cli.IntFlag{
302+
Name: "tests",
303+
Usage: "Number of tests to run in the batch round",
304+
Aliases: []string{"t"},
305+
Value: 100,
306+
},
307+
},
137308
},
138309
},
139310
}

commands/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func CreateTaskAction(context *cli.Context) error {
109109
Output: config.IOType{
110110
Type: "stdout",
111111
},
112-
Languages: nil,
112+
Languages: map[string]config.LanguageDescription{},
113113
}
114114
configuration, err := config.LoadDefaultConfiguration()
115115
if err != nil {

0 commit comments

Comments
 (0)