@@ -2,6 +2,19 @@ package cmd
2
2
3
3
import (
4
4
"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"
5
18
6
19
"github.com/markhuang1212/code-grader/backend/types"
7
20
)
@@ -13,10 +26,105 @@ type ExecUserCodeResult struct {
13
26
Msg string
14
27
}
15
28
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
+ }()
17
96
18
- result := & ExecUserCodeResult
97
+ programLength := len (program )
98
+ fmt .Fprintln (hjresp .Conn , strconv .Itoa (programLength ))
99
+ hjresp .Conn .Write (program )
100
+ hjresp .Close ()
19
101
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
+ }
21
129
22
130
}
0 commit comments