Skip to content

Commit

Permalink
feat: add deno adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
thezzisu committed Apr 20, 2024
1 parent d3e8678 commit 11ce102
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 1 deletion.
198 changes: 198 additions & 0 deletions adapters/judgers/deno/deno.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package deno

import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"

"github.com/fedstackjs/azukiiro/common"
"github.com/fedstackjs/azukiiro/judge"
"github.com/fedstackjs/azukiiro/storage"
"github.com/fedstackjs/azukiiro/utils"
"github.com/sirupsen/logrus"
)

func init() {
judge.RegisterAdapter(&DenoAdapter{})
}

type DenoAdapterConfig struct {
Script string `json:"script"`
Timeout int `json:"timeout"`
}

type DenoAdapter struct{}

func (g *DenoAdapter) Name() string {
return "deno"
}

func (g *DenoAdapter) reportHandler(ctx context.Context, task judge.JudgeTask, pipe *os.File) {
reader := bufio.NewReader(pipe)
request := common.SolutionInfo{}
for {
line, _, err := reader.ReadLine()
if err != nil {
return
}
k, v, err := parseKVLine(line)
if err != nil {
logrus.Warnf("Failed to parse report line: %v", err)
continue
}
switch k {
case "score":
score, err := strconv.ParseFloat(v, 64)
if err != nil {
logrus.Warnf("Failed to parse score: %v", err)
continue
}
if score < 0 || score > 100 {
logrus.Warnf("Invalid score: %v", score)
continue
}
request.Score = score
case "status":
request.Status = v
case "message":
request.Message = v
case "metrics":
metrics := make(map[string]float64)
if err := json.Unmarshal([]byte(v), &metrics); err != nil {
logrus.Warnf("Failed to parse metrics: %v", err)
continue
}
request.Metrics = &metrics
case "commit":
if err := task.Update(ctx, &request); err != nil {
logrus.Warnf("Failed to commit report: %v", err)
}
}
}
}

func (g *DenoAdapter) Judge(ctx context.Context, task judge.JudgeTask) error {
config := task.Config()

adapterConfig := DenoAdapterConfig{}
if err := json.Unmarshal([]byte(config.Judge.Config), &adapterConfig); err != nil {
return err
}

problemDir, err := utils.Unzip(task.ProblemData(), "problem")
if err != nil {
return err
}
defer os.RemoveAll(problemDir)

scriptPath := filepath.Join(problemDir, adapterConfig.Script)
if _, err := os.Stat(scriptPath); err != nil {
return err
}

solutionDir, err := utils.Unzip(task.SolutionData(), "solution")
if err != nil {
return err
}
defer os.RemoveAll(solutionDir)

workDir, err := storage.MkdirTemp("deno-*")
if err != nil {
return err
}
defer os.RemoveAll(workDir)

detailsPath := filepath.Join(workDir, "details.json")
details := common.SolutionDetails{
Version: 1,
Jobs: []*common.SolutionDetailsJob{},
Summary: "",
}
detailsJson, err := json.Marshal(details)
if err != nil {
return err
}
if err := os.WriteFile(detailsPath, detailsJson, 0600); err != nil {
return err
}

reportPath := filepath.Join(workDir, "report")
if err := syscall.Mkfifo(reportPath, 0600); err != nil {

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, arm64)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, amd64)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, arm, 7)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, 386)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, 386)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, arm64)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, amd64)

undefined: syscall.Mkfifo

Check failure on line 129 in adapters/judgers/deno/deno.go

View workflow job for this annotation

GitHub Actions / build (windows, arm, 7)

undefined: syscall.Mkfifo
return err
}
defer os.Remove(reportPath)
pipe, err := os.OpenFile(reportPath, os.O_RDWR, 0600)
if err != nil {
return err
}
defer pipe.Close()

go g.reportHandler(ctx, task, pipe)

execCtx, cancel := context.WithTimeout(ctx, time.Duration(adapterConfig.Timeout)*time.Second)
defer cancel()
name := "deno"
args := []string{"run"}
args = append(args, "--allow-read="+strings.Join([]string{problemDir, solutionDir}, ","))
args = append(args, "--allow-write="+strings.Join([]string{reportPath, detailsPath}, ","))
args = append(args, "--no-prompt")
args = append(args, additionalDenoArgs...)
args = append(args, scriptPath)

cmd := exec.CommandContext(execCtx, name, args...)
cmd.Dir = workDir
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "AZUKIIRO_PROBLEM_DATA_DIR="+problemDir)
cmd.Env = append(cmd.Env, "AZUKIIRO_SOLUTION_DATA_DIR="+solutionDir)
cmd.Env = append(cmd.Env, "AZUKIIRO_REPORT="+reportPath)
cmd.Env = append(cmd.Env, "AZUKIIRO_DETAILS="+detailsPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmdErr := cmd.Run()

detailsJson, err = os.ReadFile(detailsPath)
if err != nil {
logrus.Warnf("Failed to read details: %v", err)
}
if err := json.Unmarshal(detailsJson, &details); err != nil {
logrus.Warnf("Failed to unmarshal details: %v", err)
}

if cmdErr != nil {
if err := task.Update(ctx, &common.SolutionInfo{
Score: 0,
Status: "Judge Error",
Message: "Judge process exited abnormally",
}); err != nil {
logrus.Warnf("Failed to report error: %v", err)
}

details.Summary += "\n\n"
details.Summary += fmt.Sprintf("Judge process exited abnormally: %v", cmdErr)
}

if err := task.UploadDetails(ctx, &details); err != nil {
logrus.Warnf("Failed to save details: %v", err)
}

return nil
}

func parseKVLine(line []byte) (string, string, error) {
parts := bytes.SplitN(line, []byte("="), 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid line: does not contain key and value separated by '='")
}
key := string(parts[0])
value := string(parts[1])
return key, value, nil
}
7 changes: 7 additions & 0 deletions adapters/judgers/deno/deno_safe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build !unsafe

package deno

var (
additionalDenoArgs = []string{"--no-remote", "--allow-env", "--allow-hrtime"}
)
7 changes: 7 additions & 0 deletions adapters/judgers/deno/deno_unsafe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build unsafe

package deno

var (
additionalDenoArgs = []string{"--allow-all"}
)
1 change: 1 addition & 0 deletions adapters/judgers/judger.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package judgers

import (
_ "github.com/fedstackjs/azukiiro/adapters/judgers/deno"
_ "github.com/fedstackjs/azukiiro/adapters/judgers/dummy"
_ "github.com/fedstackjs/azukiiro/adapters/judgers/glue"
_ "github.com/fedstackjs/azukiiro/adapters/judgers/uoj"
Expand Down
4 changes: 3 additions & 1 deletion adapters/judgers/uoj/uoj.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ func (u *UojAdapter) Judge(ctx context.Context, task judge.JudgeTask) error {
adapterConfig := UOJAdapterConfig{
SandboxMode: "bwrap",
}
json.Unmarshal([]byte(config.Judge.Config), &adapterConfig)
if err := json.Unmarshal([]byte(config.Judge.Config), &adapterConfig); err != nil {
return err
}

judgerPath := "/opt/uoj_judger"

Expand Down

0 comments on commit 11ce102

Please sign in to comment.