aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/game/hub.go274
-rw-r--r--backend/taskqueue/processor.go148
-rw-r--r--backend/taskqueue/queue.go4
-rw-r--r--backend/taskqueue/tasks.go6
4 files changed, 305 insertions, 127 deletions
diff --git a/backend/game/hub.go b/backend/game/hub.go
index b51d977..d17ff7c 100644
--- a/backend/game/hub.go
+++ b/backend/game/hub.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"log"
+ "strings"
"time"
"github.com/jackc/pgx/v5/pgtype"
@@ -204,36 +205,273 @@ func (hub *gameHub) run() {
}
}
+type codeSubmissionError struct {
+ Status string
+ Stdout string
+ Stderr string
+}
+
+func (err *codeSubmissionError) Error() string {
+ return err.Stderr
+}
+
func (hub *gameHub) processTaskResults() {
for taskResult := range hub.taskResults {
switch taskResult := taskResult.(type) {
case *taskqueue.TaskResultCreateSubmissionRecord:
- // todo
+ err := hub.processTaskResultCreateSubmissionRecord(taskResult)
+ if err != nil {
+ for player := range hub.players {
+ if player.playerID != taskResult.TaskPayload.UserID() {
+ continue
+ }
+ player.s2cMessages <- &playerMessageS2CExecResult{
+ Type: playerMessageTypeS2CExecResult,
+ Data: playerMessageS2CExecResultPayload{
+ Score: nil,
+ Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status),
+ },
+ }
+ }
+ // TODO: broadcast to watchers
+ }
case *taskqueue.TaskResultCompileSwiftToWasm:
- // todo
+ err := hub.processTaskResultCompileSwiftToWasm(taskResult)
+ if err != nil {
+ for player := range hub.players {
+ if player.playerID != taskResult.TaskPayload.UserID() {
+ continue
+ }
+ player.s2cMessages <- &playerMessageS2CExecResult{
+ Type: playerMessageTypeS2CExecResult,
+ Data: playerMessageS2CExecResultPayload{
+ Score: nil,
+ Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status),
+ },
+ }
+ }
+ // TODO: broadcast to watchers
+ }
case *taskqueue.TaskResultCompileWasmToNativeExecutable:
- // todo
- case *taskqueue.TaskResultRunTestcase:
- // todo
- for player := range hub.players {
- if player.playerID != taskResult.TaskPayload.UserID() {
- continue
+ err := hub.processTaskResultCompileWasmToNativeExecutable(taskResult)
+ if err != nil {
+ for player := range hub.players {
+ if player.playerID != taskResult.TaskPayload.UserID() {
+ continue
+ }
+ player.s2cMessages <- &playerMessageS2CExecResult{
+ Type: playerMessageTypeS2CExecResult,
+ Data: playerMessageS2CExecResultPayload{
+ Score: nil,
+ Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status),
+ },
+ }
}
- player.s2cMessages <- &playerMessageS2CExecResult{
- Type: playerMessageTypeS2CExecResult,
- Data: playerMessageS2CExecResultPayload{
- Score: nil,
- Status: api.GamePlayerMessageS2CExecResultPayloadStatus(taskResult.Status),
- },
+ // TODO: broadcast to watchers
+ }
+ case *taskqueue.TaskResultRunTestcase:
+ err := hub.processTaskResultRunTestcase(taskResult)
+ if err != nil {
+ for player := range hub.players {
+ if player.playerID != taskResult.TaskPayload.UserID() {
+ continue
+ }
+ player.s2cMessages <- &playerMessageS2CExecResult{
+ Type: playerMessageTypeS2CExecResult,
+ Data: playerMessageS2CExecResultPayload{
+ Score: nil,
+ Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status),
+ },
+ }
}
+ // TODO: broadcast to watchers
}
- // broadcast to watchers
+ // TODO: aggregate results of testcases
default:
panic("unexpected task result type")
}
}
}
+func (hub *gameHub) processTaskResultCreateSubmissionRecord(
+ taskResult *taskqueue.TaskResultCreateSubmissionRecord,
+) *codeSubmissionError {
+ if taskResult.Err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: taskResult.Err.Error(),
+ }
+ }
+
+ if err := hub.taskQueue.EnqueueTaskCompileSwiftToWasm(
+ taskResult.TaskPayload.GameID(),
+ taskResult.TaskPayload.UserID(),
+ taskResult.TaskPayload.Code(),
+ taskResult.SubmissionID,
+ ); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return nil
+}
+
+func (hub *gameHub) processTaskResultCompileSwiftToWasm(
+ taskResult *taskqueue.TaskResultCompileSwiftToWasm,
+) *codeSubmissionError {
+ if taskResult.Err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: taskResult.Err.Error(),
+ }
+ }
+
+ if taskResult.Status != "success" {
+ if err := hub.q.CreateSubmissionResult(hub.ctx, db.CreateSubmissionResultParams{
+ SubmissionID: int32(taskResult.TaskPayload.SubmissionID),
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return &codeSubmissionError{
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }
+ }
+ if err := hub.taskQueue.EnqueueTaskCompileWasmToNativeExecutable(
+ taskResult.TaskPayload.GameID(),
+ taskResult.TaskPayload.UserID(),
+ taskResult.TaskPayload.Code(),
+ taskResult.TaskPayload.SubmissionID,
+ ); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return nil
+}
+
+func (hub *gameHub) processTaskResultCompileWasmToNativeExecutable(
+ taskResult *taskqueue.TaskResultCompileWasmToNativeExecutable,
+) *codeSubmissionError {
+ if taskResult.Err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: taskResult.Err.Error(),
+ }
+ }
+
+ if taskResult.Status != "success" {
+ if err := hub.q.CreateSubmissionResult(hub.ctx, db.CreateSubmissionResultParams{
+ SubmissionID: int32(taskResult.TaskPayload.SubmissionID),
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return &codeSubmissionError{
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }
+ }
+
+ testcases, err := hub.q.ListTestcasesByGameID(hub.ctx, int32(taskResult.TaskPayload.GameID()))
+ if err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ if len(testcases) == 0 {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: "no testcases found",
+ }
+ }
+
+ for _, testcase := range testcases {
+ if err := hub.taskQueue.EnqueueTaskRunTestcase(
+ taskResult.TaskPayload.GameID(),
+ taskResult.TaskPayload.UserID(),
+ taskResult.TaskPayload.Code(),
+ taskResult.TaskPayload.SubmissionID,
+ int(testcase.TestcaseID),
+ testcase.Stdin,
+ testcase.Stdout,
+ ); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ }
+ return nil
+}
+
+func (hub *gameHub) processTaskResultRunTestcase(
+ taskResult *taskqueue.TaskResultRunTestcase,
+) *codeSubmissionError {
+ if taskResult.Err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: taskResult.Err.Error(),
+ }
+ }
+
+ if taskResult.Status != "success" {
+ if err := hub.q.CreateTestcaseResult(hub.ctx, db.CreateTestcaseResultParams{
+ SubmissionID: int32(taskResult.TaskPayload.SubmissionID),
+ TestcaseID: int32(taskResult.TaskPayload.TestcaseID),
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return &codeSubmissionError{
+ Status: taskResult.Status,
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }
+ }
+ if !isTestcaseResultCorrect(taskResult.TaskPayload.Stdout, taskResult.Stdout) {
+ if err := hub.q.CreateTestcaseResult(hub.ctx, db.CreateTestcaseResultParams{
+ SubmissionID: int32(taskResult.TaskPayload.SubmissionID),
+ TestcaseID: int32(taskResult.TaskPayload.TestcaseID),
+ Status: "wrong_answer",
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }); err != nil {
+ return &codeSubmissionError{
+ Status: "internal_error",
+ Stderr: err.Error(),
+ }
+ }
+ return &codeSubmissionError{
+ Status: "wrong_answer",
+ Stdout: taskResult.Stdout,
+ Stderr: taskResult.Stderr,
+ }
+ }
+ return nil
+}
+
func (hub *gameHub) startGame() error {
for player := range hub.players {
player.s2cMessages <- &playerMessageS2CPrepare{
@@ -376,3 +614,9 @@ func (hubs *GameHubs) StartGame(gameID int) error {
}
return hub.startGame()
}
+
+func isTestcaseResultCorrect(expectedStdout, actualStdout string) bool {
+ expectedStdout = strings.TrimSpace(expectedStdout)
+ actualStdout = strings.TrimSpace(actualStdout)
+ return actualStdout == expectedStdout
+}
diff --git a/backend/taskqueue/processor.go b/backend/taskqueue/processor.go
index d771e61..ba35a1b 100644
--- a/backend/taskqueue/processor.go
+++ b/backend/taskqueue/processor.go
@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "strings"
"github.com/nsfisis/iosdc-japan-2024-albatross/backend/db"
)
@@ -46,6 +45,7 @@ func (p *processor) doProcessTaskCompileSwiftToWasm(
ctx context.Context,
payload *TaskPayloadCompileSwiftToWasm,
) (*TaskResultCompileSwiftToWasm, error) {
+ _ = ctx
type swiftcRequestData struct {
MaxDuration int `json:"max_duration_ms"`
Code string `json:"code"`
@@ -71,27 +71,9 @@ func (p *processor) doProcessTaskCompileSwiftToWasm(
if err := json.NewDecoder(res.Body).Decode(&resData); err != nil {
return nil, fmt.Errorf("json.Decode failed: %v", err)
}
- if resData.Status != "success" {
- err := p.q.CreateSubmissionResult(ctx, db.CreateSubmissionResultParams{
- SubmissionID: int32(payload.SubmissionID),
- Status: "compile_error",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- })
- if err != nil {
- return nil, fmt.Errorf("CreateTestcaseResult failed: %v", err)
- }
- return &TaskResultCompileSwiftToWasm{
- TaskPayload: payload,
- Status: "compile_error",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- }, nil
- }
-
return &TaskResultCompileSwiftToWasm{
TaskPayload: payload,
- Status: "success",
+ Status: resData.Status,
Stdout: resData.Stdout,
Stderr: resData.Stderr,
}, nil
@@ -101,6 +83,7 @@ func (p *processor) doProcessTaskCompileWasmToNativeExecutable(
ctx context.Context,
payload *TaskPayloadCompileWasmToNativeExecutable,
) (*TaskResultCompileWasmToNativeExecutable, error) {
+ _ = ctx
type wasmcRequestData struct {
MaxDuration int `json:"max_duration_ms"`
Code string `json:"code"`
@@ -126,27 +109,9 @@ func (p *processor) doProcessTaskCompileWasmToNativeExecutable(
if err := json.NewDecoder(res.Body).Decode(&resData); err != nil {
return nil, fmt.Errorf("json.Decode failed: %v", err)
}
- if resData.Status != "success" {
- err := p.q.CreateSubmissionResult(ctx, db.CreateSubmissionResultParams{
- SubmissionID: int32(payload.SubmissionID),
- Status: "compile_error",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- })
- if err != nil {
- return nil, fmt.Errorf("CreateTestcaseResult failed: %v", err)
- }
- return &TaskResultCompileWasmToNativeExecutable{
- TaskPayload: payload,
- Status: "compile_error",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- }, nil
- }
-
return &TaskResultCompileWasmToNativeExecutable{
TaskPayload: payload,
- Status: "success",
+ Status: resData.Status,
Stdout: resData.Stdout,
Stderr: resData.Stderr,
}, nil
@@ -156,75 +121,40 @@ func (p *processor) doProcessTaskRunTestcase(
ctx context.Context,
payload *TaskPayloadRunTestcase,
) (*TaskResultRunTestcase, error) {
- testcases, err := p.q.ListTestcasesByGameID(ctx, int32(payload.GameID()))
+ type testrunRequestData struct {
+ MaxDuration int `json:"max_duration_ms"`
+ Code string `json:"code"`
+ Stdin string `json:"stdin"`
+ }
+ type testrunResponseData struct {
+ Status string `json:"status"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+ }
+ reqData := testrunRequestData{
+ MaxDuration: 5000,
+ Code: payload.Code(),
+ Stdin: payload.Stdin,
+ }
+ reqJson, err := json.Marshal(reqData)
if err != nil {
- return nil, fmt.Errorf("ListTestcasesByGameID failed: %v", err)
+ return nil, fmt.Errorf("json.Marshal failed: %v", err)
}
-
- for _, testcase := range testcases {
- type testrunRequestData struct {
- MaxDuration int `json:"max_duration_ms"`
- Code string `json:"code"`
- Stdin string `json:"stdin"`
- }
- type testrunResponseData struct {
- Status string `json:"status"`
- Stdout string `json:"stdout"`
- Stderr string `json:"stderr"`
- }
- reqData := testrunRequestData{
- MaxDuration: 5000,
- Code: payload.Code(),
- Stdin: testcase.Stdin,
- }
- reqJson, err := json.Marshal(reqData)
- if err != nil {
- return nil, fmt.Errorf("json.Marshal failed: %v", err)
- }
- res, err := http.Post("http://worker:80/api/testrun", "application/json", bytes.NewBuffer(reqJson))
- if err != nil {
- return nil, fmt.Errorf("http.Post failed: %v", err)
- }
- resData := testrunResponseData{}
- if err := json.NewDecoder(res.Body).Decode(&resData); err != nil {
- return nil, fmt.Errorf("json.Decode failed: %v", err)
- }
- if resData.Status != "success" {
- err := p.q.CreateTestcaseResult(ctx, db.CreateTestcaseResultParams{
- SubmissionID: int32(payload.SubmissionID),
- TestcaseID: testcase.TestcaseID,
- Status: resData.Status,
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- })
- if err != nil {
- return nil, fmt.Errorf("CreateTestcaseResult failed: %v", err)
- }
- return &TaskResultRunTestcase{
- TaskPayload: payload,
- Status: resData.Status,
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- }, nil
- }
- if !isTestcaseResultCorrect(testcase.Stdout, resData.Stdout) {
- err := p.q.CreateTestcaseResult(ctx, db.CreateTestcaseResultParams{
- SubmissionID: int32(payload.SubmissionID),
- TestcaseID: testcase.TestcaseID,
- Status: "wrong_answer",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- })
- if err != nil {
- return nil, fmt.Errorf("CreateTestcaseResult failed: %v", err)
- }
- return &TaskResultRunTestcase{
- TaskPayload: payload,
- Status: "wrong_answer",
- Stdout: resData.Stdout,
- Stderr: resData.Stderr,
- }, nil
- }
+ res, err := http.Post("http://worker:80/api/testrun", "application/json", bytes.NewBuffer(reqJson))
+ if err != nil {
+ return nil, fmt.Errorf("http.Post failed: %v", err)
+ }
+ resData := testrunResponseData{}
+ if err := json.NewDecoder(res.Body).Decode(&resData); err != nil {
+ return nil, fmt.Errorf("json.Decode failed: %v", err)
+ }
+ if resData.Status != "success" {
+ return &TaskResultRunTestcase{
+ TaskPayload: payload,
+ Status: resData.Status,
+ Stdout: resData.Stdout,
+ Stderr: resData.Stderr,
+ }, nil
}
return &TaskResultRunTestcase{
@@ -232,9 +162,3 @@ func (p *processor) doProcessTaskRunTestcase(
Status: "success",
}, nil
}
-
-func isTestcaseResultCorrect(expectedStdout, actualStdout string) bool {
- expectedStdout = strings.TrimSpace(expectedStdout)
- actualStdout = strings.TrimSpace(actualStdout)
- return actualStdout == expectedStdout
-}
diff --git a/backend/taskqueue/queue.go b/backend/taskqueue/queue.go
index b7d7381..515a406 100644
--- a/backend/taskqueue/queue.go
+++ b/backend/taskqueue/queue.go
@@ -83,6 +83,8 @@ func (q *Queue) EnqueueTaskRunTestcase(
code string,
submissionID int,
testcaseID int,
+ stdin string,
+ stdout string,
) error {
task, err := newTaskRunTestcase(
gameID,
@@ -90,6 +92,8 @@ func (q *Queue) EnqueueTaskRunTestcase(
code,
submissionID,
testcaseID,
+ stdin,
+ stdout,
)
if err != nil {
return err
diff --git a/backend/taskqueue/tasks.go b/backend/taskqueue/tasks.go
index 990ce65..cbe83b1 100644
--- a/backend/taskqueue/tasks.go
+++ b/backend/taskqueue/tasks.go
@@ -112,6 +112,8 @@ type TaskPayloadRunTestcase struct {
TaskPayloadBase
SubmissionID int
TestcaseID int
+ Stdin string
+ Stdout string
}
func newTaskRunTestcase(
@@ -120,6 +122,8 @@ func newTaskRunTestcase(
code string,
submissionID int,
testcaseID int,
+ stdin string,
+ stdout string,
) (*asynq.Task, error) {
payload, err := json.Marshal(TaskPayloadRunTestcase{
TaskPayloadBase: TaskPayloadBase{
@@ -129,6 +133,8 @@ func newTaskRunTestcase(
},
SubmissionID: submissionID,
TestcaseID: testcaseID,
+ Stdin: stdin,
+ Stdout: stdout,
})
if err != nil {
return nil, err