From 0abcdf176d5a8bc420a40fe679771c8309131186 Mon Sep 17 00:00:00 2001 From: Filip Gregor Date: Tue, 30 Sep 2025 00:18:19 +0200 Subject: [PATCH 1/3] feat: add go --- Makefile | 2 +- evaluator/README.md | 11 +++++++++++ evaluator/config/config.yaml | 14 ++++++++++++++ evaluator/config/userspace.cfg | 2 +- evaluator/infra/languages/config.toml | 3 +++ evaluator/infra/languages/go/setup.sh | 22 ++++++++++++++++++++++ evaluator/src/eval.rs | 1 + website/src/lib/constants.ts | 7 +++++++ website/src/lib/defaultPrograms.ts | 19 +++++++++++++++++++ website/src/lib/types.ts | 1 + 10 files changed, 80 insertions(+), 2 deletions(-) create mode 100755 evaluator/infra/languages/go/setup.sh diff --git a/Makefile b/Makefile index 05c39eb..013dbc1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: build prepare up all test -up: +up: build docker compose up prepare: diff --git a/evaluator/README.md b/evaluator/README.md index 78a9ec2..24757a9 100644 --- a/evaluator/README.md +++ b/evaluator/README.md @@ -2,3 +2,14 @@ Can be run with `cargo build` and `cargo run`. Do note that it currently requires root priviledges, since it edits cgroups. + +## Language nuances + +Some languages (their compilers or interpreters) have some nuances that need to be taken into account. + +### Go + +Go needs `GOCACHE` to be set to a folder. +The compiler then caches some files there. + +For each Go version, we create a folder in `/opt/evaluator/compilers/go//cache` and run `go build std` for each version. The /opt/evaluator folder is mounted as RW volume, which is enough for the cache. diff --git a/evaluator/config/config.yaml b/evaluator/config/config.yaml index 8752ac7..8e10a24 100644 --- a/evaluator/config/config.yaml +++ b/evaluator/config/config.yaml @@ -88,3 +88,17 @@ commands: - cp ${SOURCE_FILE} source.mjl - ${EXECUTOR} interpret source.mjl + +- name: go + compilers: + - name: 1.25.1 + path: /opt/evaluator/compilers/go/1.25.1/bin/go + - name: 1.24.7 + path: /opt/evaluator/compilers/go/1.24.7/bin/go + - name: 1.23.12 + path: /opt/evaluator/compilers/go/1.23.12/bin/go + commands: + - cp ${SOURCE_FILE} source.go + - GOCACHE="/opt/evaluator/compilers/go/${COMPILER_NAME}/.gocache" ${COMPILER} build -o source ${COMPILER_ARGS} source.go + - rm source.go + - ./source ${SOURCE_ARGS} diff --git a/evaluator/config/userspace.cfg b/evaluator/config/userspace.cfg index a268e91..11cffae 100644 --- a/evaluator/config/userspace.cfg +++ b/evaluator/config/userspace.cfg @@ -30,7 +30,7 @@ gidmap { detect_cgroupv2: true cgroup_mem_max: 1073741824 # 1GB -cgroup_pids_max: 16 # Haskell needs at least this much. +cgroup_pids_max: 256 # Before Go, 16 was enough... cgroup_cpu_ms_per_sec: 200 # create dummy user, some programs (racket) need it diff --git a/evaluator/infra/languages/config.toml b/evaluator/infra/languages/config.toml index 4bff71e..de90017 100644 --- a/evaluator/infra/languages/config.toml +++ b/evaluator/infra/languages/config.toml @@ -6,3 +6,6 @@ image = "rust:1.79-bookworm" [racket] image = "gcc:13.2-bookworm" + +[go] +image = "debian:bookworm-slim" diff --git a/evaluator/infra/languages/go/setup.sh b/evaluator/infra/languages/go/setup.sh new file mode 100755 index 0000000..6825bf9 --- /dev/null +++ b/evaluator/infra/languages/go/setup.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e + +apt-get update +apt-get install -y wget + +COMPILERS_DIR="/opt/evaluator/compilers/go" + +setup_go_version() { + GO_VERSION=$1 + echo "Setting up go version $GO_VERSION" + GO_FTP="https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" + echo "Downloading $GO_FTP" + wget "${GO_FTP}" + tar -xzf go${GO_VERSION}.linux-amd64.tar.gz + mkdir -p "${COMPILERS_DIR}" + mv go "${COMPILERS_DIR}/${GO_VERSION}" + GOCACHE="${COMPILERS_DIR}/${GO_VERSION}/.gocache" "${COMPILERS_DIR}/${GO_VERSION}/bin/go" build -v std +} + +setup_go_version "1.25.1" +setup_go_version "1.24.7" +setup_go_version "1.23.12" diff --git a/evaluator/src/eval.rs b/evaluator/src/eval.rs index 488f9db..3b3ef20 100644 --- a/evaluator/src/eval.rs +++ b/evaluator/src/eval.rs @@ -172,6 +172,7 @@ async fn execute(spec: &RunSpec, payload: &RequestPayload, eval_id: &str) -> Res .iter() .find(|c| c.name == *compiler) .with_context(|| format!("Compiler '{}' not found", compiler))?; + command = str::replace(&command, "${COMPILER_NAME}", &compiler_spec.name); let compiler_path = match &compiler_spec.path { Some(path) => path.to_owned(), None => format!("{}/{}/{}", *COMPILERS_PATH, spec.name, compiler_spec.name), diff --git a/website/src/lib/constants.ts b/website/src/lib/constants.ts index f0fb1cb..cd2673f 100644 --- a/website/src/lib/constants.ts +++ b/website/src/lib/constants.ts @@ -36,6 +36,13 @@ export const languages: { [key in LangKey]: ILanguage } = { text: "Bash", executors: ["bash-bookworm"], }, + go: { + name: "go", + server_name: "go", + editor_name: "go", + text: "Go", + compilers: ["1.25.1", "1.24.7", "1.23.12"], + }, c: { name: "c", server_name: "c", diff --git a/website/src/lib/defaultPrograms.ts b/website/src/lib/defaultPrograms.ts index 959d4c7..2e89322 100644 --- a/website/src/lib/defaultPrograms.ts +++ b/website/src/lib/defaultPrograms.ts @@ -45,6 +45,25 @@ export const defaultPrograms: IPrograms = { "print(fact(5))", ].join("\n"), shell: ["#!/bin/bash", "", "echo 'Hello, World!'"].join("\n"), + go: [ + "package main", + "", + "import (", + ' "fmt"', + ")", + "", + "func fact(n int) int {", + " if n == 0 {", + " return 1", + " } else {", + " return n * fact(n - 1)", + " }", + "}", + "", + "func main() {", + " fmt.Println(fact(5))", + "}", + ].join("\n"), c: [ "#include ", "", diff --git a/website/src/lib/types.ts b/website/src/lib/types.ts index 2e696e6..51d343b 100644 --- a/website/src/lib/types.ts +++ b/website/src/lib/types.ts @@ -41,6 +41,7 @@ export type LangKey = | "python3" | "racket" | "bash" + | "go" | "c" | "cpp" | "rust" From b1560da84b0d1759010ec107e9c21e17594731d3 Mon Sep 17 00:00:00 2001 From: Filip Gregor <44952616+Gregofi@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:30:02 +0200 Subject: [PATCH 2/3] Update evaluator/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- evaluator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evaluator/README.md b/evaluator/README.md index 24757a9..e07f951 100644 --- a/evaluator/README.md +++ b/evaluator/README.md @@ -12,4 +12,4 @@ Some languages (their compilers or interpreters) have some nuances that need to Go needs `GOCACHE` to be set to a folder. The compiler then caches some files there. -For each Go version, we create a folder in `/opt/evaluator/compilers/go//cache` and run `go build std` for each version. The /opt/evaluator folder is mounted as RW volume, which is enough for the cache. +For each Go version, we create a folder in `/opt/evaluator/compilers/go//.gocache` and run `go build std` for each version. The /opt/evaluator folder is mounted as RW volume, which is enough for the cache. From f3983c829556de5597ae007b2248fceddd9408b0 Mon Sep 17 00:00:00 2001 From: Filip Gregor Date: Tue, 30 Sep 2025 11:33:37 +0200 Subject: [PATCH 3/3] add go test --- evaluator/infra/languages/go/setup.sh | 2 +- integration_tests/evaluator_tests/test_go.py | 22 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 integration_tests/evaluator_tests/test_go.py diff --git a/evaluator/infra/languages/go/setup.sh b/evaluator/infra/languages/go/setup.sh index 6825bf9..c287b9d 100755 --- a/evaluator/infra/languages/go/setup.sh +++ b/evaluator/infra/languages/go/setup.sh @@ -11,7 +11,7 @@ setup_go_version() { GO_FTP="https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" echo "Downloading $GO_FTP" wget "${GO_FTP}" - tar -xzf go${GO_VERSION}.linux-amd64.tar.gz + tar -xzf "go${GO_VERSION}.linux-amd64.tar.gz" mkdir -p "${COMPILERS_DIR}" mv go "${COMPILERS_DIR}/${GO_VERSION}" GOCACHE="${COMPILERS_DIR}/${GO_VERSION}/.gocache" "${COMPILERS_DIR}/${GO_VERSION}/bin/go" build -v std diff --git a/integration_tests/evaluator_tests/test_go.py b/integration_tests/evaluator_tests/test_go.py new file mode 100644 index 0000000..e88571e --- /dev/null +++ b/integration_tests/evaluator_tests/test_go.py @@ -0,0 +1,22 @@ +import requests + +EVALUATOR_ADDRESS = "http://evaluator:7800/api/v1/evaluate" + +def generate_python(code: str): + return {"language": "go", "code": code, "compiler": "1.25.1"} + +def test_simple_python(): + payload = generate_python(""" +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +""") + response = requests.post(EVALUATOR_ADDRESS, json=payload, headers={"X-Forwarded-For": "1.1.1.1"}) + assert response.status_code == 200 + values = response.json() + assert values["stdout"] == "Hello, World!\n" + assert values["stderr"] == ""