aboutsummaryrefslogtreecommitdiffhomepage
path: root/worker/swift
diff options
context:
space:
mode:
Diffstat (limited to 'worker/swift')
-rw-r--r--worker/swift/exec_test.go149
-rw-r--r--worker/swift/handlers_test.go66
-rw-r--r--worker/swift/justfile5
-rw-r--r--worker/swift/models_test.go70
4 files changed, 289 insertions, 1 deletions
diff --git a/worker/swift/exec_test.go b/worker/swift/exec_test.go
new file mode 100644
index 0000000..ead2dc6
--- /dev/null
+++ b/worker/swift/exec_test.go
@@ -0,0 +1,149 @@
+package main
+
+import (
+ "context"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+func TestConvertCommandErrorToResultType(t *testing.T) {
+ tests := []struct {
+ name string
+ err error
+ defaultStatus string
+ want string
+ }{
+ {"nil error returns success", nil, resultRuntimeError, resultSuccess},
+ {"DeadlineExceeded returns timeout", context.DeadlineExceeded, resultRuntimeError, resultTimeout},
+ {"other error returns default status", os.ErrNotExist, resultCompileError, resultCompileError},
+ {"other error returns runtime_error default", os.ErrPermission, resultRuntimeError, resultRuntimeError},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := convertCommandErrorToResultType(tt.err, tt.defaultStatus)
+ if got != tt.want {
+ t.Errorf("convertCommandErrorToResultType() = %q, want %q", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestExecCommandWithTimeout_Success(t *testing.T) {
+ stdout, stderr, err := execCommandWithTimeout(
+ context.Background(),
+ t.TempDir(),
+ 5*time.Second,
+ func(ctx context.Context) *exec.Cmd {
+ return exec.CommandContext(ctx, "echo", "hello")
+ },
+ )
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if stdout != "hello\n" {
+ t.Errorf("stdout = %q, want %q", stdout, "hello\n")
+ }
+ if stderr != "" {
+ t.Errorf("stderr = %q, want empty", stderr)
+ }
+}
+
+func TestExecCommandWithTimeout_Failure(t *testing.T) {
+ _, _, err := execCommandWithTimeout(
+ context.Background(),
+ t.TempDir(),
+ 5*time.Second,
+ func(ctx context.Context) *exec.Cmd {
+ return exec.CommandContext(ctx, "false")
+ },
+ )
+ if err == nil {
+ t.Fatal("expected error, got nil")
+ }
+}
+
+func TestExecCommandWithTimeout_Timeout(t *testing.T) {
+ _, _, err := execCommandWithTimeout(
+ context.Background(),
+ t.TempDir(),
+ 50*time.Millisecond,
+ func(ctx context.Context) *exec.Cmd {
+ return exec.CommandContext(ctx, "sleep", "10")
+ },
+ )
+ if err != context.DeadlineExceeded {
+ t.Errorf("expected DeadlineExceeded, got %v", err)
+ }
+}
+
+func TestExecCommandWithTimeout_Stderr(t *testing.T) {
+ _, stderr, _ := execCommandWithTimeout(
+ context.Background(),
+ t.TempDir(),
+ 5*time.Second,
+ func(ctx context.Context) *exec.Cmd {
+ return exec.CommandContext(ctx, "sh", "-c", "echo errmsg >&2")
+ },
+ )
+ if stderr != "errmsg\n" {
+ t.Errorf("stderr = %q, want %q", stderr, "errmsg\n")
+ }
+}
+
+func TestPrepareWorkingDir(t *testing.T) {
+ dir := filepath.Join(t.TempDir(), "subdir")
+ res := prepareWorkingDir(dir)
+ if res.Status != resultSuccess {
+ t.Fatalf("prepareWorkingDir() status = %q, want %q", res.Status, resultSuccess)
+ }
+ info, err := os.Stat(dir)
+ if err != nil {
+ t.Fatalf("directory not created: %v", err)
+ }
+ if !info.IsDir() {
+ t.Fatalf("expected directory, got file")
+ }
+}
+
+func TestPutSwiftSourceFile(t *testing.T) {
+ t.Run("writes file when Sources/ exists", func(t *testing.T) {
+ dir := t.TempDir()
+ sourcesDir := filepath.Join(dir, "Sources")
+ if err := os.MkdirAll(sourcesDir, 0755); err != nil {
+ t.Fatalf("failed to create Sources dir: %v", err)
+ }
+ res := putSwiftSourceFile(dir, "print(\"hello\")")
+ if res.Status != resultSuccess {
+ t.Fatalf("putSwiftSourceFile() status = %q, want %q", res.Status, resultSuccess)
+ }
+ content, err := os.ReadFile(filepath.Join(sourcesDir, "main.swift"))
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+ if string(content) != "print(\"hello\")" {
+ t.Errorf("file content = %q, want %q", string(content), "print(\"hello\")")
+ }
+ })
+
+ t.Run("returns error when Sources/ does not exist", func(t *testing.T) {
+ dir := t.TempDir()
+ res := putSwiftSourceFile(dir, "print(\"hello\")")
+ if res.Status == resultSuccess {
+ t.Fatal("expected error status, got success")
+ }
+ })
+}
+
+func TestRemoveWorkingDir(t *testing.T) {
+ dir := filepath.Join(t.TempDir(), "toremove")
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Fatalf("failed to create dir: %v", err)
+ }
+ removeWorkingDir(dir)
+ if _, err := os.Stat(dir); !os.IsNotExist(err) {
+ t.Errorf("directory still exists after removeWorkingDir")
+ }
+}
diff --git a/worker/swift/handlers_test.go b/worker/swift/handlers_test.go
new file mode 100644
index 0000000..f9c2e0b
--- /dev/null
+++ b/worker/swift/handlers_test.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/labstack/echo/v4"
+)
+
+func TestNewBadRequestError(t *testing.T) {
+ httpErr := newBadRequestError(errors.New("test error"))
+ if httpErr.Code != http.StatusBadRequest {
+ t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest)
+ }
+ msg, ok := httpErr.Message.(string)
+ if !ok {
+ t.Fatalf("expected string message, got %T", httpErr.Message)
+ }
+ if !strings.Contains(msg, "test error") {
+ t.Errorf("message = %q, want it to contain %q", msg, "test error")
+ }
+}
+
+func TestHandleExec_InvalidJSON(t *testing.T) {
+ e := echo.New()
+ req := httptest.NewRequest(http.MethodPost, "/exec", strings.NewReader("not json"))
+ req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(req, rec)
+
+ err := handleExec(c)
+ if err == nil {
+ t.Fatal("expected error, got nil")
+ }
+ httpErr, ok := err.(*echo.HTTPError)
+ if !ok {
+ t.Fatalf("expected *echo.HTTPError, got %T", err)
+ }
+ if httpErr.Code != http.StatusBadRequest {
+ t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest)
+ }
+}
+
+func TestHandleExec_ZeroMaxDuration(t *testing.T) {
+ e := echo.New()
+ body := `{"code":"print(1)","code_hash":"abc","stdin":"","max_duration_ms":0}`
+ req := httptest.NewRequest(http.MethodPost, "/exec", strings.NewReader(body))
+ req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(req, rec)
+
+ err := handleExec(c)
+ if err == nil {
+ t.Fatal("expected error, got nil")
+ }
+ httpErr, ok := err.(*echo.HTTPError)
+ if !ok {
+ t.Fatalf("expected *echo.HTTPError, got %T", err)
+ }
+ if httpErr.Code != http.StatusBadRequest {
+ t.Errorf("status code = %d, want %d", httpErr.Code, http.StatusBadRequest)
+ }
+}
diff --git a/worker/swift/justfile b/worker/swift/justfile
index b203597..decaaf0 100644
--- a/worker/swift/justfile
+++ b/worker/swift/justfile
@@ -2,4 +2,7 @@ check:
go build -o /dev/null ./...
go tool golangci-lint run
-ci: check
+test:
+ go test ./...
+
+ci: check test
diff --git a/worker/swift/models_test.go b/worker/swift/models_test.go
new file mode 100644
index 0000000..62d6675
--- /dev/null
+++ b/worker/swift/models_test.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "testing"
+ "time"
+)
+
+func TestExecRequestData_Validate(t *testing.T) {
+ tests := []struct {
+ name string
+ maxDurationMs int
+ wantErr error
+ }{
+ {"positive value", 1000, nil},
+ {"zero", 0, errInvalidMaxDuration},
+ {"negative value", -1, errInvalidMaxDuration},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := &execRequestData{MaxDurationMilliseconds: tt.maxDurationMs}
+ err := req.validate()
+ if err != tt.wantErr {
+ t.Errorf("validate() = %v, want %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestExecRequestData_MaxDuration(t *testing.T) {
+ tests := []struct {
+ name string
+ maxDurationMs int
+ want time.Duration
+ }{
+ {"1000ms", 1000, 1 * time.Second},
+ {"500ms", 500, 500 * time.Millisecond},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := &execRequestData{MaxDurationMilliseconds: tt.maxDurationMs}
+ got := req.maxDuration()
+ if got != tt.want {
+ t.Errorf("maxDuration() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestExecResponseData_Success(t *testing.T) {
+ tests := []struct {
+ name string
+ status string
+ want bool
+ }{
+ {"success", resultSuccess, true},
+ {"compile_error", resultCompileError, false},
+ {"runtime_error", resultRuntimeError, false},
+ {"timeout", resultTimeout, false},
+ {"internal_error", resultInternalError, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ res := &execResponseData{Status: tt.status}
+ got := res.success()
+ if got != tt.want {
+ t.Errorf("success() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}