Skip to content

Commit 20ed32d

Browse files
new test case
1 parent d41944f commit 20ed32d

File tree

19 files changed

+234
-84
lines changed

19 files changed

+234
-84
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
"hjresp",
1010
"lpthread",
1111
"prepend",
12+
"printf",
1213
"stdc",
1314
"struct",
15+
"testcase",
16+
"testcases",
1417
"usercode"
1518
]
1619
}

backend/cmd/compile_user_code.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"strconv"
78
"time"
@@ -22,10 +23,10 @@ type CompileUserCodeResult struct {
2223
Msg string
2324
}
2425

25-
const CompilationMemoryLimit = 100 * 1024 * 1024
26+
const CompilationMemoryLimit = 256 * 1024 * 1024
2627
const CompilationTimeLimit = 10 * time.Second
2728

28-
var ErrCompilationError = errors.New("compilation error")
29+
// var ErrCompilationError = errors.New("compilation error")
2930

3031
// it is required that the docker image is built before the program runs
3132
const imageCompile = "markhuang1212/code-grader/runtime-compile:latest"
@@ -36,14 +37,18 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
3637

3738
result := &CompileUserCodeResult{}
3839

39-
ctxdl, cancel := context.WithTimeout(ctx, CompilationTimeLimit)
40-
defer cancel()
40+
if !IsTestcase(gr.TestCaseName) {
41+
return nil, types.ErrNoTestCase
42+
}
4143

4244
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
4345
if err != nil {
4446
return nil, errors.WithMessage(err, "cannot create docker client")
4547
}
4648

49+
ctxdl, cancel := context.WithTimeout(ctx, CompilationTimeLimit)
50+
defer cancel()
51+
4752
resp, err := cli.ContainerCreate(ctxdl, &container.Config{
4853
Image: imageCompile,
4954
Env: []string{
@@ -76,7 +81,7 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
7681

7782
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
7883
if err != nil {
79-
return nil, errors.Wrap(ErrCompilationError, "cannot start container")
84+
return nil, errors.Wrap(err, "cannot start container")
8085
}
8186

8287
defer func() {
@@ -90,14 +95,10 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
9095
}()
9196

9297
userCodeLength := len(gr.UserCode)
93-
hjresp.Conn.Write([]byte(strconv.Itoa(userCodeLength) + "\n"))
98+
fmt.Fprintln(hjresp.Conn, strconv.Itoa(userCodeLength))
9499
hjresp.Conn.Write([]byte(gr.UserCode))
95100
hjresp.Close()
96101

97-
if err != nil {
98-
return nil, errors.Wrap(err, "cannot close attached session (output)")
99-
}
100-
101102
select {
102103
case status := <-statusCh:
103104
switch status.StatusCode {
@@ -126,9 +127,9 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
126127
result.Ok = false
127128
return result, nil
128129
case 2:
129-
return nil, ErrCompilationError
130+
return nil, types.ErrInternal
130131
default:
131-
return nil, ErrCompilationError
132+
return nil, types.ErrInternal
132133
}
133134
case err := <-errCh:
134135
return nil, errors.Wrap(err, "error waiting container")

backend/cmd/compile_user_code_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,11 @@ func TestCompileUserCode(t *testing.T) {
3232
assert.True(t, result.Ok)
3333
t.Log(result)
3434

35+
gr3 := types.GradeRequest{
36+
TestCaseName: "some-random-name",
37+
UserCode: " ",
38+
}
39+
_, err = cmd.CompileUserCode(ctx, gr3)
40+
assert.ErrorIs(t, err, types.ErrNoTestCase)
41+
3542
}

backend/cmd/exec_user_code.go

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ package cmd
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
"path/filepath"
11+
"strconv"
12+
"time"
13+
14+
dockertypes "github.com/docker/docker/api/types"
15+
"github.com/docker/docker/api/types/container"
16+
"github.com/docker/docker/client"
17+
"github.com/pkg/errors"
518

619
"github.com/markhuang1212/code-grader/backend/types"
720
)
@@ -13,10 +26,105 @@ type ExecUserCodeResult struct {
1326
Msg string
1427
}
1528

16-
func ExecUserCode(ctx context.Context, gr types.GradeRequest) (*ExecUserCodeResult, error) {
29+
func ExecUserCode(ctx context.Context, gr types.GradeRequest, program []byte) (*ExecUserCodeResult, error) {
30+
31+
result := &ExecUserCodeResult{}
32+
33+
if !IsTestcase(gr.TestCaseName) {
34+
return nil, types.ErrNoTestCase
35+
}
36+
37+
testcaseConfJson, err := os.ReadFile(filepath.Join(GetAppRoot(), "testcases", gr.TestCaseName, "testcase.json"))
38+
if err != nil {
39+
return nil, errors.Wrap(err, "cannot read testcase.json")
40+
}
41+
42+
testcaseConf := types.TestCaseOptions{}
43+
err = json.Unmarshal(testcaseConfJson, &testcaseConf)
44+
if err != nil {
45+
return nil, errors.Wrap(err, "cannot parse testcase.json")
46+
}
47+
48+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
49+
if err != nil {
50+
return nil, errors.WithMessage(err, "cannot create docker client")
51+
}
52+
53+
ctxdl, cancel := context.WithTimeout(ctx, time.Duration(testcaseConf.RuntimeOptions.RuntimeLimit)*time.Second)
54+
defer cancel()
55+
56+
resp, err := cli.ContainerCreate(ctxdl, &container.Config{
57+
Image: imageExec,
58+
Env: []string{
59+
"TEST_CASE_DIR=" + filepath.Join("/code-grader/testcases", gr.TestCaseName),
60+
},
61+
OpenStdin: true,
62+
}, &container.HostConfig{
63+
NetworkMode: "none",
64+
Resources: container.Resources{
65+
MemorySwap: int64(testcaseConf.RuntimeOptions.MemoryLimit) * 1024 * 1024,
66+
},
67+
}, nil, nil, "")
68+
if err != nil {
69+
return nil, errors.Wrap(err, "cannot create container")
70+
}
71+
72+
hjresp, err := cli.ContainerAttach(ctxdl, resp.ID, dockertypes.ContainerAttachOptions{
73+
Stdin: true,
74+
Stream: true,
75+
})
76+
if err != nil {
77+
return nil, errors.Wrap(err, "cannot attach container")
78+
}
79+
80+
statusCh, errCh := cli.ContainerWait(ctxdl, resp.ID, container.WaitConditionNextExit)
81+
82+
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
83+
if err != nil {
84+
return nil, errors.Wrap(err, "cannot start container")
85+
}
86+
87+
defer func() {
88+
err := cli.ContainerRemove(ctxdl, resp.ID, dockertypes.ContainerRemoveOptions{
89+
Force: true,
90+
})
91+
if err != nil {
92+
log.Panicln("cannot kill and remove container")
93+
panic(err)
94+
}
95+
}()
1796

18-
result := &ExecUserCodeResult
97+
programLength := len(program)
98+
fmt.Fprintln(hjresp.Conn, strconv.Itoa(programLength))
99+
hjresp.Conn.Write(program)
100+
hjresp.Close()
19101

20-
return result, nil
102+
select {
103+
case status := <-statusCh:
104+
switch status.StatusCode {
105+
case 0:
106+
result.Ok = true
107+
result.Msg = "correct answer"
108+
return result, nil
109+
case 1, 2:
110+
stderr, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
111+
ShowStderr: true,
112+
ShowStdout: true,
113+
})
114+
if err != nil {
115+
return nil, errors.Wrap(err, "error reading stdout")
116+
}
117+
text, _ := io.ReadAll(stderr)
118+
result.Msg = string(text)
119+
result.Ok = false
120+
return result, nil
121+
case 3:
122+
return nil, types.ErrInternal
123+
default:
124+
return nil, types.ErrInternal
125+
}
126+
case err := <-errCh:
127+
return nil, errors.Wrap(err, "error waiting container")
128+
}
21129

22130
}

backend/cmd/load_testcases.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,51 @@
11
package cmd
22

33
import (
4+
"log"
45
"os"
56
"path/filepath"
6-
7-
"github.com/pkg/errors"
7+
"sort"
88
)
99

10+
var testcases []string
11+
1012
// Load all existing test case names
11-
func LoadTestcases() ([]string, error) {
13+
func LoadTestcases() []string {
1214

13-
result := []string{}
15+
if testcases == nil {
1416

15-
dir := filepath.Join(GetAppRoot(), "testcases")
16-
ret, err := os.ReadDir(dir)
17+
dir := filepath.Join(GetAppRoot(), "testcases")
18+
ret, err := os.ReadDir(dir)
1719

18-
if err != nil {
19-
return nil, errors.Wrap(err, "cannot read testcase dir")
20-
}
20+
if err != nil {
21+
log.Fatal("cannot read testcases")
22+
}
2123

22-
for _, fileEntry := range ret {
23-
if fileEntry.IsDir() {
24-
result = append(result, fileEntry.Name())
24+
for _, fileEntry := range ret {
25+
if fileEntry.IsDir() {
26+
testcases = append(testcases, fileEntry.Name())
27+
}
2528
}
29+
30+
sort.Strings(testcases)
31+
2632
}
2733

28-
return result, nil
34+
return testcases
35+
}
36+
37+
func IsTestcase(name string) bool {
38+
39+
if testcases == nil {
40+
LoadTestcases()
41+
}
42+
43+
idx := sort.SearchStrings(testcases, name)
44+
45+
if idx == len(testcases) || testcases[idx] != name {
46+
return false
47+
}
48+
49+
return true
50+
2951
}

backend/cmd/load_testcases_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import (
88
)
99

1010
func TestLoadTestcases(t *testing.T) {
11-
ret, err := cmd.LoadTestcases()
12-
assert.Equal(t, err, nil)
11+
ret := cmd.LoadTestcases()
1312
for _, v := range ret {
1413
if v == "example-1" {
1514
return
1615
}
1716
}
1817
t.Errorf("wrong result")
1918
}
19+
20+
func TestIsTestcase(t *testing.T) {
21+
ret := cmd.IsTestcase("example-1")
22+
assert.True(t, ret)
23+
}

backend/types/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package types
2+
3+
import "errors"
4+
5+
var ErrNoTestCase = errors.New("no such test case")
6+
var ErrInternal = errors.New("internal error")

runtime-exec/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ chmod +x a.out
1515
if [ -z "${TEST_CASE_DIR}" ]
1616
then
1717
echo "Missing TEST_CASE_DIR"
18-
exit 2
18+
exit 3
1919
fi
2020

2121
a.out < ${TEST_CASE_DIR}/input.txt > output.txt

testcases/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Writing Test Cases
2+
3+
To add testcases to this folder, Please follow guidelines given in folders `example-x`. Upon commit, test cases will be automatically tested. Please make sure the answer can be run within the given runtime and memory.

testcases/bst-find-elem/README.md

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)