aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-28 17:00:35 +0900
committernsfisis <nsfisis@gmail.com>2026-02-28 17:00:35 +0900
commitb15f06ece6a9dac01a06688be39e437cfa8ed257 (patch)
tree2a713f2079d7055f5777c84db29f9c25bf867e33
parent10f0cd9dbc408b171fbd6a96ed1b552407046b13 (diff)
downloadphperkaigi-2026-albatross-b15f06ece6a9dac01a06688be39e437cfa8ed257.tar.gz
phperkaigi-2026-albatross-b15f06ece6a9dac01a06688be39e437cfa8ed257.tar.zst
phperkaigi-2026-albatross-b15f06ece6a9dac01a06688be39e437cfa8ed257.zip
fix(game): normalize CRLF to LF in stdin and code before execution
DB stores stdin/code with CRLF intact, but workers expect LF. Add normalizeCRLF helper and apply it to code and stdin in EnqueueTestTasks. Refactor normalizeTestcaseResultOutput to reuse the same helper.
-rw-r--r--backend/game/hub.go11
-rw-r--r--backend/game/hub_test.go53
2 files changed, 60 insertions, 4 deletions
diff --git a/backend/game/hub.go b/backend/game/hub.go
index 8c0a574..3caa6d4 100644
--- a/backend/game/hub.go
+++ b/backend/game/hub.go
@@ -71,8 +71,8 @@ func (hub *Hub) EnqueueTestTasks(ctx context.Context, submissionID, gameID, user
submissionID,
int(row.TestcaseID),
language,
- code,
- row.Stdin,
+ normalizeCRLF(code),
+ normalizeCRLF(row.Stdin),
row.Stdout,
)
if err != nil {
@@ -175,9 +175,12 @@ func (hub *Hub) processTaskResultRunTestcase(
return nil
}
+func normalizeCRLF(s string) string {
+ return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", "\n"), "\r", "\n")
+}
+
func normalizeTestcaseResultOutput(s string) string {
- re := regexp.MustCompile(`\r\n|\r`)
- return re.ReplaceAllString(strings.TrimSpace(s), "\n")
+ return normalizeCRLF(strings.TrimSpace(s))
}
func isTestcaseResultCorrect(expectedStdout, actualStdout string) bool {
diff --git a/backend/game/hub_test.go b/backend/game/hub_test.go
index 5aa440b..1d493b9 100644
--- a/backend/game/hub_test.go
+++ b/backend/game/hub_test.go
@@ -249,6 +249,59 @@ func TestNormalizeTestcaseResultOutput(t *testing.T) {
}
}
+func TestNormalizeCRLF(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {"CRLF to LF", "hello\r\nworld", "hello\nworld"},
+ {"CR to LF", "hello\rworld", "hello\nworld"},
+ {"LF unchanged", "hello\nworld", "hello\nworld"},
+ {"lone CRLF", "\r\n", "\n"},
+ {"empty string", "", ""},
+ {"multiple CRLF", "a\r\nb\r\nc", "a\nb\nc"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := normalizeCRLF(tt.input)
+ if got != tt.want {
+ t.Errorf("normalizeCRLF(%q) = %q, want %q", tt.input, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestEnqueueTestTasks_NormalizesCRLF(t *testing.T) {
+ testcases := []db.Testcase{
+ {TestcaseID: 1, ProblemID: 10, Stdin: "input\r\n", Stdout: "output"},
+ }
+
+ tq := &mockTaskQueue{}
+ mq := &mockQuerier{
+ listTestcasesByGameIDFunc: func(_ context.Context, _ int32) ([]db.Testcase, error) {
+ return testcases, nil
+ },
+ }
+
+ hub := &Hub{q: mq, taskQueue: tq, ctx: context.Background()}
+
+ err := hub.EnqueueTestTasks(context.Background(), 100, 1, 42, "php", "<?php\r\necho 1;")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if len(tq.enqueued) != 1 {
+ t.Fatalf("expected 1 enqueued task, got %d", len(tq.enqueued))
+ }
+ if tq.enqueued[0].Stdin != "input\n" {
+ t.Errorf("expected stdin CRLF normalized, got %q", tq.enqueued[0].Stdin)
+ }
+ if tq.enqueued[0].Code != "<?php\necho 1;" {
+ t.Errorf("expected code CRLF normalized, got %q", tq.enqueued[0].Code)
+ }
+}
+
func TestCalcCodeSize_PHP(t *testing.T) {
hub := &Hub{}
tests := []struct {