Skip to content

Commit 3396c29

Browse files
Merge pull request #62 from CS3219-AY2425S1/solomon/code-execute
Solomon/code execute
2 parents f77bbfa + 911ff0f commit 3396c29

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+5061
-4573
lines changed

.github/workflows/test.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ jobs:
2424
env:
2525
QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
2626
JWT_SECRET: ${{ secrets.JWT_SECRET }}
27+
EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }}
2728
run: |
2829
cd ./apps/question-service
29-
3030
echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env
3131
echo "JWT_SECRET=$JWT_SECRET" >> .env
32+
echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env
3233
3334
- name: Set up credentials
3435
env:
@@ -52,7 +53,7 @@ jobs:
5253
run: curl -sL firebase.tools | bash
5354

5455
- name: Run Go tests with Firebase emulator
55-
run: firebase emulators:exec --only firestore 'cd ./apps/question-service; go test ./...'
56+
run: firebase emulators:exec --only firestore 'cd ./apps/question-service; go test -v ./tests'
5657

5758
frontend-unit-tests:
5859
runs-on: ubuntu-latest
@@ -103,14 +104,17 @@ jobs:
103104
MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }}
104105
HISTORY_SERVICE_URL: ${{ vars.HISTORY_SERVICE_URL }}
105106
SIGNALLING_SERVICE_URL: ${{ vars.SIGNALLING_SERVICE_URL }}
107+
EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }}
106108
JWT_SECRET: ${{ secrets.JWT_SECRET }}
107109
QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
108110
HISTORY_FIREBASE_CREDENTIAL_PATH: ${{ vars.HISTORY_SERVICE_FIREBASE_CREDENTIAL_PATH }}
111+
EXECUTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.EXECUTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
109112
DB_CLOUD_URI: ${{ secrets.USER_SERVICE_DB_CLOUD_URI }}
110113
USER_SERVICE_PORT: ${{ vars.USER_SERVICE_PORT }}
111114
MATCHING_SERVICE_PORT: ${{ vars.MATCHING_SERVICE_PORT }}
112115
HISTORY_SERVICE_PORT: ${{ vars.HISTORY_SERVICE_PORT }}
113116
SIGNALLING_SERVICE_PORT: ${{ vars.SIGNALLING_SERVICE_PORT }}
117+
EXECUTION_SERVICE_PORT: ${{ vars.EXECUTION_SERVICE_PORT }}
114118
MATCHING_SERVICE_TIMEOUT: ${{ vars.MATCHING_SERVICE_TIMEOUT }}
115119
REDIS_URL: ${{ vars.REDIS_URL }}
116120
QUESTION_SERVICE_GRPC_URL: ${{ vars.QUESTION_SERVICE_GPRC_URL }}
@@ -121,10 +125,12 @@ jobs:
121125
echo "NEXT_PUBLIC_MATCHING_SERVICE_URL=$MATCHING_SERVICE_URL" >> .env
122126
echo "NEXT_PUBLIC_HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env
123127
echo "NEXT_PUBLIC_SIGNALLING_SERVICE_URL=$SIGNALLING_SERVICE_URL" >> .env
128+
echo "NEXT_PUBLIC_EXECUTION_SERVICE_URL=EXECUTION_SERVICE_URL" >> .env
124129
125130
cd ../question-service
126131
echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env
127132
echo "JWT_SECRET=$JWT_SECRET" >> .env
133+
echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env
128134
129135
cd ../user-service
130136
echo "DB_CLOUD_URI=$DB_CLOUD_URI" >> .env
@@ -141,6 +147,11 @@ jobs:
141147
cd ../history-service
142148
echo "FIREBASE_CREDENTIAL_PATH=$HISTORY_FIREBASE_CREDENTIAL_PATH" >> .env
143149
echo "PORT=$HISTORY_SERVICE_PORT" >> .env
150+
151+
cd ../execution-service
152+
echo "FIREBASE_CREDENTIAL_PATH=$EXECUTION_FIREBASE_CREDENTIAL_PATH" >> .env
153+
echo "PORT=$EXECUTION_SERVICE_PORT" >> .env
154+
echo "HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env
144155
145156
cd ../signalling-service
146157
echo "PORT=$SIGNALLING_SERVICE_PORT" >> .env
@@ -151,12 +162,17 @@ jobs:
151162
QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
152163
HISTORY_FIREBASE_JSON: ${{ secrets.HISTORY_SERVICE_FIREBASE_CREDENTIAL }}
153164
HISTORY_FIREBASE_CREDENTIAL_PATH: ${{ vars.HISTORY_SERVICE_FIREBASE_CREDENTIAL_PATH }}
165+
EXECUTION_FIREBASE_JSON: ${{ secrets.EXECUTION_SERVICE_FIREBASE_CREDENTIAL }}
166+
EXECUTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.EXECUTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
154167
run: |
155168
cd ./apps/question-service
156169
echo "$QUESTION_FIREBASE_JSON" > "./$QUESTION_FIREBASE_CREDENTIAL_PATH"
157170
158171
cd ../history-service
159172
echo "$HISTORY_FIREBASE_JSON" > "./$HISTORY_FIREBASE_CREDENTIAL_PATH"
173+
174+
cd ../execution-service
175+
echo "$EXECUTION_FIREBASE_JSON" > "./$EXECUTION_FIREBASE_CREDENTIAL_PATH"
160176
161177
- name: Build and Run Services
162178
run: |
@@ -180,6 +196,7 @@ jobs:
180196
MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }}
181197
HISTORY_SERVICE_URL: ${{ vars.HISTORY_SERVICE_URL }}
182198
SIGNALLING_SERVICE_URL: ${{ vars.SIGNALLING_SERVICE_URL }}
199+
EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }}
183200
run: |
184201
echo "Testing Question Service..."
185202
curl -sSL -o /dev/null $QUESTION_SERVICE_URL && echo "Question Service is up"
@@ -189,6 +206,8 @@ jobs:
189206
curl -fsSL -o /dev/null $FRONTEND_URL && echo "Frontend is up"
190207
echo "Testing History Service..."
191208
curl -fsSL -o /dev/null $HISTORY_SERVICE_URL && echo "History Service is up"
209+
echo "Testing Execution Service..."
210+
curl -fsSL -o /dev/null $EXECUTION_SERVICE_URL && echo "Execution Service is up"
192211
echo "Testing Matching Service..."
193212
if ! (echo "Hello" | websocat $MATCHING_SERVICE_URL); then
194213
echo "WebSocket for Matching Service is not live"

apps/docker-compose.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ services:
8282
volumes:
8383
- ./signalling-service:/signalling-service
8484

85+
execution-service:
86+
build:
87+
context: ./execution-service
88+
dockerfile: Dockerfile
89+
ports:
90+
- 8083:8083
91+
env_file:
92+
- ./execution-service/.env
93+
networks:
94+
- apps_network
95+
volumes:
96+
- ./execution-service:/execution-service
97+
- /var/run/docker.sock:/var/run/docker.sock
98+
8599
redis:
86100
image: redis:latest
87101
networks:
@@ -90,5 +104,13 @@ services:
90104
- 6379:6379
91105
container_name: redis-container
92106

107+
python-sandbox:
108+
build:
109+
context: ./execution-service/execution/python
110+
dockerfile: Dockerfile
111+
networks:
112+
- apps_network
113+
container_name: python-sandbox
114+
93115
networks:
94116
apps_network:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.env.example
2+
3+
.git
4+
.gitignore
5+
6+
.dockerignore
7+
Dockerfile
8+
9+
README.md
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FIREBASE_CREDENTIAL_PATH=cs3219-staging-codeexecution-firebase-adminsdk-ce48j-00ab09514c.json
2+
PORT=8083
3+
4+
# If you are NOT USING docker, use the below variables
5+
# HISTORY_SERVICE_URL=http://localhost:8082/
6+
7+
# If you are USING docker, use the below variables
8+
HISTORY_SERVICE_URL=http://history-service:8082/

apps/execution-service/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
cs3219-staging-codeexecution-firebase-adminsdk-ce48j-00ab09514c.json

apps/execution-service/Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FROM golang:1.23
2+
3+
WORKDIR /usr/src/app
4+
5+
# Install Docker CLI
6+
RUN apt-get update && apt-get install -y \
7+
docker.io \
8+
&& apt-get clean \
9+
&& rm -rf /var/lib/apt/lists/*
10+
11+
# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
12+
COPY go.mod go.sum ./
13+
14+
RUN go mod tidy && go mod download && go mod verify
15+
16+
COPY . .
17+
18+
RUN go build -v -o /usr/local/bin/app ./main.go
19+
20+
EXPOSE 8083
21+
22+
CMD ["app"]

apps/execution-service/README.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Execution Service
2+
3+
### Installation
4+
5+
1. Install dependencies:
6+
7+
```bash
8+
go mod tidy
9+
```
10+
11+
2. Create the `.env` file from copying the `.env.example`, and copy the firebase JSON file into execution-service/ fill in the `FIREBASE_CREDENTIAL_PATH` with the path of the firebase credential JSON file.
12+
13+
### Running the Application
14+
15+
To start the server, run the following command:
16+
17+
```bash
18+
go run main.go
19+
```
20+
21+
The server will be available at http://localhost:8083.
22+
23+
## Running the Application via Docker
24+
25+
To run the application via Docker, run the following command:
26+
27+
```bash
28+
docker build -t execution-service .
29+
```
30+
31+
```bash
32+
docker run -p 8083:8083 --env-file .env -d execution-service
33+
```
34+
35+
The server will be available at http://localhost:8083.
36+
37+
## API Endpoints
38+
39+
- `POST /tests/populate`
40+
- `GET /tests/{questionDocRefId}/`
41+
- `POST /tests/{questionDocRefId}/execute`
42+
- `POST /tests/{questionDocRefId}/submit`
43+
44+
## Managing Firebase
45+
46+
To reset and repopulate the database, run the following command:
47+
48+
```bash
49+
go run main.go
50+
```
51+
52+
## Repopulate test cases
53+
54+
To repopulate test cases, you need to repopulate the questions in the question-service, which will automatically call the execution-service to populate the test cases.
55+
56+
In question-service, run the following command:
57+
58+
```bash
59+
go run main.go -populate
60+
```
61+
62+
## API Documentation
63+
64+
`GET /tests/{questionDocRefId}/`
65+
66+
To read visible test cases via a question ID, run the following command:
67+
68+
```bash
69+
curl -X GET http://localhost:8083/tests/{questioinDocRefId}/ \
70+
-H "Content-Type: application/json"
71+
```
72+
73+
The following json format will be returned:
74+
75+
```json
76+
[
77+
{
78+
"input":"hello",
79+
"expected":"olleh"
80+
}
81+
]
82+
```
83+
84+
`POST /tests/{questionDocRefId}/execute`
85+
86+
To execute test cases via a question ID without custom test cases, run the following command, with custom code and language:
87+
88+
```bash
89+
curl -X POST http://localhost:8083/tests/{questioinDocRefId}/execute \
90+
-H "Content-Type: application/json" \
91+
-d '{
92+
"code": "name = input()\nprint(name[::-1])",
93+
"language": "Python"
94+
}'
95+
```
96+
97+
The following json format will be returned:
98+
99+
```json
100+
{
101+
"visibleTestResults":[
102+
{
103+
"input":"hello",
104+
"expected":"olleh",
105+
"actual":"olleh",
106+
"passed":true,
107+
"error":""
108+
}
109+
],
110+
"customTestResults":null
111+
}
112+
```
113+
114+
To execute visible and custom test cases via a question ID with custom test cases, run the following command, with custom code, language and custom test cases:
115+
116+
```bash
117+
curl -X POST http://localhost:8083/tests/{questioinDocRefId}/execute \
118+
-H "Content-Type: application/json" \
119+
-d '{
120+
"code": "name = input()\nprint(name[::-1])",
121+
"language": "Python",
122+
"customTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba\n"
123+
}'
124+
```
125+
126+
The following json format will be returned:
127+
128+
```json
129+
{
130+
"visibleTestResults":[
131+
{
132+
"input":"hello",
133+
"expected":"olleh",
134+
"actual":"olleh",
135+
"passed":true,
136+
"error":""
137+
}
138+
],
139+
"customTestResults":[
140+
{
141+
"input":"Hannah",
142+
"expected":"hannaH",
143+
"actual":"hannaH",
144+
"passed":true,
145+
"error":""
146+
},
147+
{
148+
"input":"abcdefg",
149+
"expected":"gfedcba",
150+
"actual":"gfedcba",
151+
"passed":true,
152+
"error":""
153+
}
154+
]
155+
}
156+
```
157+
158+
`POST /tests/{questionDocRefId}/submit`
159+
160+
To submit a solution and execute visible and hidden test cases via a question ID, run the following command, with custom code and language:
161+
162+
```bash
163+
curl -X POST http://localhost:8083/tests/{questioinDocRefId}/submit \
164+
-H "Content-Type: application/json" \
165+
-d '{
166+
"title": "Example Title",
167+
"code": "name = input()\nprint(name[::-1])",
168+
"language": "Python",
169+
"user": "user123",
170+
"matchedUser": "user456",
171+
"matchedTopics": ["topic1", "topic2"],
172+
"questionDifficulty": "Medium",
173+
"questionTopics": ["Loops", "Strings"]
174+
}'
175+
```
176+
177+
The following json format will be returned:
178+
179+
```json
180+
{
181+
"visibleTestResults":[
182+
{
183+
"input":"hello",
184+
"expected":"olleh",
185+
"actual":"olleh",
186+
"passed":true,
187+
"error":""
188+
}
189+
],
190+
"hiddenTestResults":{
191+
"passed":2,
192+
"total":2
193+
},
194+
"status":"Accepted"
195+
}
196+
```
197+
198+
If compilation error exists or any of the tests (visible and hidden) fails, status "Attempted" will be returned:
199+
200+
```json
201+
{
202+
"visibleTestResults":[
203+
{
204+
"input":"hello",
205+
"expected":"olleh",
206+
"actual":"",
207+
"passed":false,
208+
"error":"Command execution failed: Traceback (most recent call last):\n File \"/tmp/4149249165.py\", line 2, in \u003cmodule\u003e\n prit(name[::-1])\n ^^^^\nNameError: name 'prit' is not defined. Did you mean: 'print'?\n: %!w(*exec.ExitError=\u0026{0x4000364678 []})"
209+
}
210+
],
211+
"hiddenTestResults":{
212+
"passed":0,
213+
"total":2
214+
},
215+
"status":"Attempted"
216+
}
217+
```

0 commit comments

Comments
 (0)