diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-08-08 01:30:09 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-08-08 04:02:37 +0900 |
| commit | 4eb7e89d6a77a4434bd087fbb86873521d30a8f5 (patch) | |
| tree | da13030d94538170a931747b957d2865bed03682 | |
| parent | 26378c2d2ad1f8cb3f20c7070be3be9a4f0a0ad6 (diff) | |
| download | iosdc-japan-2025-albatross-4eb7e89d6a77a4434bd087fbb86873521d30a8f5.tar.gz iosdc-japan-2025-albatross-4eb7e89d6a77a4434bd087fbb86873521d30a8f5.tar.zst iosdc-japan-2025-albatross-4eb7e89d6a77a4434bd087fbb86873521d30a8f5.zip | |
feat(backend): implement processTaskResults() partially
| -rw-r--r-- | backend/game/hub.go | 274 | ||||
| -rw-r--r-- | backend/taskqueue/processor.go | 148 | ||||
| -rw-r--r-- | backend/taskqueue/queue.go | 4 | ||||
| -rw-r--r-- | backend/taskqueue/tasks.go | 6 |
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 |
