Skip to content

Commit 5a47882

Browse files
finish GradeUserCode
1 parent 20ed32d commit 5a47882

File tree

10 files changed

+201
-88
lines changed

10 files changed

+201
-88
lines changed

backend/cmd/compile_user_code.go

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ package cmd
22

33
import (
44
"context"
5-
"fmt"
65
"io"
7-
"strconv"
6+
"os"
87
"time"
98

109
"log"
@@ -18,9 +17,8 @@ import (
1817
)
1918

2019
type CompileUserCodeResult struct {
21-
Ok bool
22-
Result []byte
23-
Msg string
20+
Ok bool
21+
Msg string
2422
}
2523

2624
const CompilationMemoryLimit = 256 * 1024 * 1024
@@ -33,14 +31,19 @@ const imageCompile = "markhuang1212/code-grader/runtime-compile:latest"
3331

3432
// The function compiles user's code inside a docker container, and returns the
3533
// executable on success
36-
func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCodeResult, error) {
34+
func CompileUserCode(ctx context.Context, gr types.GradeRequest, tmpDir string) (*CompileUserCodeResult, error) {
3735

3836
result := &CompileUserCodeResult{}
3937

4038
if !IsTestcase(gr.TestCaseName) {
4139
return nil, types.ErrNoTestCase
4240
}
4341

42+
err := os.WriteFile(filepath.Join(tmpDir, "code.txt"), []byte(gr.UserCode), 0666)
43+
if err != nil {
44+
return nil, errors.Wrap(err, "cannot write code.txt")
45+
}
46+
4447
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
4548
if err != nil {
4649
return nil, errors.WithMessage(err, "cannot create docker client")
@@ -56,27 +59,19 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
5659
"CXX=g++",
5760
"CXXFLAGS=-std=c++11",
5861
},
59-
OpenStdin: true,
6062
}, &container.HostConfig{
63+
Binds: []string{tmpDir + ":/data"},
6164
NetworkMode: "none",
62-
Resources: container.Resources{
63-
// Memory: CompilationMemoryLimit,
65+
Resources: container.Resources{
66+
Memory: CompilationMemoryLimit,
67+
MemorySwap: CompilationMemoryLimit,
6468
},
6569
}, nil, nil, "")
6670

6771
if err != nil {
6872
return nil, errors.Wrap(err, "cannot create container")
6973
}
7074

71-
hjresp, err := cli.ContainerAttach(ctxdl, resp.ID, dockertypes.ContainerAttachOptions{
72-
Stdin: true,
73-
Stream: true,
74-
})
75-
76-
if err != nil {
77-
return nil, errors.Wrap(err, "cannot attach container")
78-
}
79-
8075
statusCh, errCh := cli.ContainerWait(ctxdl, resp.ID, container.WaitConditionNextExit)
8176

8277
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
@@ -94,35 +89,22 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) (*CompileUserCo
9489
}
9590
}()
9691

97-
userCodeLength := len(gr.UserCode)
98-
fmt.Fprintln(hjresp.Conn, strconv.Itoa(userCodeLength))
99-
hjresp.Conn.Write([]byte(gr.UserCode))
100-
hjresp.Close()
101-
10292
select {
10393
case status := <-statusCh:
10494
switch status.StatusCode {
10595
case 0:
106-
stdout, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
107-
ShowStdout: true,
108-
ShowStderr: false,
109-
})
110-
if err != nil {
111-
return nil, errors.Wrap(err, "error reading stdout")
112-
}
113-
program, _ := io.ReadAll(stdout)
11496
result.Ok = true
115-
result.Result = program
97+
result.Msg = "compilation success"
11698
return result, nil
11799
case 1:
118-
stderr, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
100+
out, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
119101
ShowStderr: true,
120-
ShowStdout: false,
102+
ShowStdout: true,
121103
})
122104
if err != nil {
123105
return nil, errors.Wrap(err, "error reading stdout")
124106
}
125-
errText, _ := io.ReadAll(stderr)
107+
errText, _ := io.ReadAll(out)
126108
result.Msg = string(errText)
127109
result.Ok = false
128110
return result, nil

backend/cmd/compile_user_code_test.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,56 @@ package cmd_test
22

33
import (
44
"context"
5+
"io/ioutil"
6+
"os"
57
"testing"
68

79
"github.com/markhuang1212/code-grader/backend/cmd"
810
"github.com/markhuang1212/code-grader/backend/types"
911
"github.com/stretchr/testify/assert"
1012
)
1113

12-
func TestCompileUserCode(t *testing.T) {
14+
func TestCompileUserCode1(t *testing.T) {
1315
ctx := context.Background()
1416

17+
tmpDir, err := ioutil.TempDir("/tmp", "")
18+
assert.Nil(t, err)
19+
defer os.RemoveAll(tmpDir)
20+
21+
err = os.Chmod(tmpDir, 0777)
22+
assert.Nil(t, err)
23+
1524
gr1 := types.GradeRequest{
1625
TestCaseName: "example-1",
1726
UserCode: " ",
1827
}
1928

20-
result, err := cmd.CompileUserCode(ctx, gr1)
29+
result, err := cmd.CompileUserCode(ctx, gr1, tmpDir)
2130
assert.Nil(t, err)
2231
assert.False(t, result.Ok)
2332
t.Log(result)
2433

34+
}
35+
36+
func TestCompileUserCode2(t *testing.T) {
37+
38+
ctx := context.Background()
39+
40+
tmpDir, err := ioutil.TempDir("/tmp", "")
41+
assert.Nil(t, err)
42+
defer os.RemoveAll(tmpDir)
43+
44+
err = os.Chmod(tmpDir, 0777)
45+
assert.Nil(t, err)
46+
2547
gr2 := types.GradeRequest{
2648
TestCaseName: "example-1",
2749
UserCode: "int main() { cout << \"Hello\" << endl; }",
2850
}
2951

30-
result, err = cmd.CompileUserCode(ctx, gr2)
52+
result, err := cmd.CompileUserCode(ctx, gr2, tmpDir)
3153
assert.Nil(t, err)
3254
assert.True(t, result.Ok)
3355
t.Log(result)
3456

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-
4257
}

backend/cmd/exec_user_code.go

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ package cmd
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
76
"io"
87
"log"
98
"os"
109
"path/filepath"
11-
"strconv"
1210
"time"
1311

1412
dockertypes "github.com/docker/docker/api/types"
@@ -22,11 +20,13 @@ import (
2220
const imageExec = "markhuang1212/code-grader/runtime-exec:latest"
2321

2422
type ExecUserCodeResult struct {
25-
Ok bool
26-
Msg string
23+
Ok bool
24+
MemoryExceed bool
25+
TimeExceed bool
26+
Msg string
2727
}
2828

29-
func ExecUserCode(ctx context.Context, gr types.GradeRequest, program []byte) (*ExecUserCodeResult, error) {
29+
func ExecUserCode(ctx context.Context, gr types.GradeRequest, tmpDir string) (*ExecUserCodeResult, error) {
3030

3131
result := &ExecUserCodeResult{}
3232

@@ -58,25 +58,18 @@ func ExecUserCode(ctx context.Context, gr types.GradeRequest, program []byte) (*
5858
Env: []string{
5959
"TEST_CASE_DIR=" + filepath.Join("/code-grader/testcases", gr.TestCaseName),
6060
},
61-
OpenStdin: true,
6261
}, &container.HostConfig{
6362
NetworkMode: "none",
63+
Binds: []string{tmpDir + ":/data"},
6464
Resources: container.Resources{
65+
Memory: int64(testcaseConf.RuntimeOptions.MemoryLimit) * 1024 * 1024,
6566
MemorySwap: int64(testcaseConf.RuntimeOptions.MemoryLimit) * 1024 * 1024,
6667
},
6768
}, nil, nil, "")
6869
if err != nil {
6970
return nil, errors.Wrap(err, "cannot create container")
7071
}
7172

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-
8073
statusCh, errCh := cli.ContainerWait(ctxdl, resp.ID, container.WaitConditionNextExit)
8174

8275
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
@@ -94,11 +87,6 @@ func ExecUserCode(ctx context.Context, gr types.GradeRequest, program []byte) (*
9487
}
9588
}()
9689

97-
programLength := len(program)
98-
fmt.Fprintln(hjresp.Conn, strconv.Itoa(programLength))
99-
hjresp.Conn.Write(program)
100-
hjresp.Close()
101-
10290
select {
10391
case status := <-statusCh:
10492
switch status.StatusCode {
@@ -107,24 +95,32 @@ func ExecUserCode(ctx context.Context, gr types.GradeRequest, program []byte) (*
10795
result.Msg = "correct answer"
10896
return result, nil
10997
case 1, 2:
110-
stderr, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
98+
out, err := cli.ContainerLogs(ctxdl, resp.ID, dockertypes.ContainerLogsOptions{
11199
ShowStderr: true,
112100
ShowStdout: true,
113101
})
114102
if err != nil {
115103
return nil, errors.Wrap(err, "error reading stdout")
116104
}
117-
text, _ := io.ReadAll(stderr)
105+
text, _ := io.ReadAll(out)
118106
result.Msg = string(text)
119107
result.Ok = false
120108
return result, nil
121109
case 3:
122110
return nil, types.ErrInternal
111+
case 137:
112+
result.Ok = false
113+
result.Msg = "memory limit exceed"
114+
result.MemoryExceed = true
115+
return result, nil
123116
default:
124117
return nil, types.ErrInternal
125118
}
126-
case err := <-errCh:
127-
return nil, errors.Wrap(err, "error waiting container")
119+
case <-errCh:
120+
result.Ok = false
121+
result.Msg = "time limit exceed"
122+
result.TimeExceed = true
123+
return result, nil
128124
}
129125

130126
}

backend/cmd/exec_user_code_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmd_test
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"os"
7+
"testing"
8+
9+
"github.com/markhuang1212/code-grader/backend/cmd"
10+
"github.com/markhuang1212/code-grader/backend/types"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestExecUserCode(t *testing.T) {
15+
16+
ctx := context.Background()
17+
18+
tmpDir, err := ioutil.TempDir("/tmp", "")
19+
assert.Nil(t, err)
20+
defer os.RemoveAll(tmpDir)
21+
22+
err = os.Chmod(tmpDir, 0777)
23+
assert.Nil(t, err)
24+
25+
gr := types.GradeRequest{
26+
TestCaseName: "example-1",
27+
UserCode: "int main() { cout << \"Hello\" << endl; }",
28+
}
29+
30+
cr, err := cmd.CompileUserCode(ctx, gr, tmpDir)
31+
assert.Nil(t, err)
32+
assert.True(t, cr.Ok)
33+
34+
er, err := cmd.ExecUserCode(ctx, gr, tmpDir)
35+
assert.Nil(t, err)
36+
assert.True(t, er.Ok)
37+
38+
}

backend/cmd/grade_user_code.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"os"
7+
8+
"github.com/markhuang1212/code-grader/backend/types"
9+
"github.com/pkg/errors"
10+
)
11+
12+
// this function wraps CompileUserCode and GradeUserCode
13+
func GradeUserCode(ctx context.Context, gr types.GradeRequest) (*types.GradeResult, error) {
14+
15+
result := types.GradeResult{}
16+
17+
tmpDir, err := ioutil.TempDir("/tmp", "cdgr_")
18+
if err != nil {
19+
return nil, errors.Wrap(err, "cannot create tmpDir")
20+
}
21+
defer os.RemoveAll(tmpDir)
22+
23+
cr, err := CompileUserCode(ctx, gr, tmpDir)
24+
if err != nil {
25+
return nil, errors.Wrap(err, "cannot compile")
26+
}
27+
28+
if !cr.Ok {
29+
result.Status = types.GradeResultCompilationError
30+
result.Msg = cr.Msg
31+
return &result, nil
32+
}
33+
34+
er, err := ExecUserCode(ctx, gr, tmpDir)
35+
if err != nil {
36+
return nil, errors.Wrap(err, "cannot execute")
37+
}
38+
39+
if !er.Ok {
40+
result.Msg = er.Msg
41+
if er.MemoryExceed {
42+
result.Status = types.GradeResultMemoryExceed
43+
} else if er.TimeExceed {
44+
result.Status = types.GradeResultTimeLimitExceed
45+
} else {
46+
result.Status = types.GradeResultWrongAnswer
47+
}
48+
return &result, nil
49+
}
50+
51+
result.Status = types.GradeResultSuccess
52+
result.Msg = "success"
53+
return &result, nil
54+
55+
}

0 commit comments

Comments
 (0)