diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 6cc8f09..a1f7d11 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -1,7 +1,8 @@
{
"permissions": {
"allow": [
- "Bash(del \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\main\\\\java\\\\cn\\\\meowrain\\\\aioj\\\\JavaDockerCodeSandBox.java\")"
+ "Bash(del \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\main\\\\java\\\\cn\\\\meowrain\\\\aioj\\\\JavaDockerCodeSandBox.java\")",
+ "Bash(if not exist \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\test\\\\java\\\\cn\\\\meowrain\\\\aioj\" mkdir \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\test\\\\java\\\\cn\\\\meowrain\\\\aioj\")"
]
}
}
diff --git a/.idea/CoolRequestCommonStatePersistent.xml b/.idea/CoolRequestCommonStatePersistent.xml
new file mode 100644
index 0000000..7fb60ef
--- /dev/null
+++ b/.idea/CoolRequestCommonStatePersistent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deployment.xml b/.idea/deployment.xml
new file mode 100644
index 0000000..710508a
--- /dev/null
+++ b/.idea/deployment.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/webServers.xml b/.idea/webServers.xml
new file mode 100644
index 0000000..197ee9e
--- /dev/null
+++ b/.idea/webServers.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e5b69a1..190f5ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,5 +42,12 @@
compile
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.11.4
+ test
+
+
\ No newline at end of file
diff --git a/src/main/java/cn/meowrain/aioj/DockerCodeSandbox.java b/src/main/java/cn/meowrain/aioj/DockerCodeSandbox.java
index 9e2aa2f..6edc62b 100644
--- a/src/main/java/cn/meowrain/aioj/DockerCodeSandbox.java
+++ b/src/main/java/cn/meowrain/aioj/DockerCodeSandbox.java
@@ -14,6 +14,7 @@ import com.github.dockerjava.api.command.ExecCreateCmdResponse; // 创建 exec
import com.github.dockerjava.api.command.InspectContainerResponse; // 容器检查响应
import com.github.dockerjava.api.command.InspectExecResponse; // exec 检查响应
import com.github.dockerjava.api.command.PullImageResultCallback; // 拉取镜像回调
+import com.github.dockerjava.api.exception.NotFoundException; // 镜像/容器不存在异常
import com.github.dockerjava.api.model.*; // Docker 模型类
import com.github.dockerjava.core.DefaultDockerClientConfig; // Docker 客户端默认配置
import com.github.dockerjava.core.DockerClientConfig; // Docker 客户端配置接口
@@ -108,10 +109,17 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
@Override
protected RunResult runCase(String input, String codePath, LanguageConfig config,
long timeLimitMs, long memoryLimitKB) throws Exception {
- // 将测试输入写入 input.txt(容器内通过 cat 读取)
+ // 将测试输入写入 input.txt
String inputFilePath = codePath + File.separator + "input.txt"; // input.txt 宿主机路径
FileUtil.writeString(input != null ? input : "", inputFilePath, StandardCharsets.UTF_8); // 写入输入内容
+ // 使用 docker cp 将 input.txt 复制到容器内 /app
+ DockerClient client = getDockerClient();
+ client.copyArchiveToContainerCmd(containerId)
+ .withHostResource(inputFilePath) // 宿主机文件路径
+ .withRemotePath(CONTAINER_WORK_DIR) // 容器内目标路径
+ .exec();
+
// 计算 JVM 堆内存限制(容器内存的 80%,最低 16MB)
long xmxMB = Math.max(16, (memoryLimitKB / 1024) * 80 / 100);
String[] runCmd = config.getRunCmd(xmxMB); // 获取运行命令(替换内存占位符)
@@ -168,10 +176,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
synchronized (DockerCodeSandbox.class) { // 加锁防止并发拉取
if (readyImages.contains(image)) return; // 双重检查
DockerClient client = getDockerClient(); // 获取客户端
- List images = client.listImagesCmd() // 查询本地镜像
- .withImageNameFilter(image) // 按名称过滤
- .exec(); // 执行查询
- if (images.isEmpty()) { // 本地不存在
+ boolean exists;
+ try {
+ client.inspectImageCmd(image).exec(); // 精确查找镜像
+ exists = true;
+ } catch (NotFoundException e) { // 镜像不存在
+ exists = false;
+ }
+ if (!exists) { // 本地不存在
System.out.println("拉取镜像: " + image); // 打印提示
try {
client.pullImageCmd(image) // 拉取镜像
@@ -198,7 +210,6 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
// 配置宿主机资源限制和安全选项
HostConfig hostConfig = HostConfig.newHostConfig()
- .withBinds(new Bind(hostCodePath, new Volume(CONTAINER_WORK_DIR))) // 挂载代码目录到 /app
.withMemory(memoryBytes) // 内存硬限制
.withMemorySwap(memoryBytes) // 禁用 swap
.withCpuCount(1L) // 限制 1 个 CPU
@@ -219,6 +230,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
String id = container.getId(); // 获取容器 ID
client.startContainerCmd(id).exec(); // 启动容器
+
+ // 使用 docker cp 将代码文件复制到容器 /app(替代 bind mount,兼容远程 Docker / WSL2)
+ client.copyArchiveToContainerCmd(id)
+ .withHostResource(hostCodePath) // 宿主机代码目录
+ .withRemotePath(CONTAINER_WORK_DIR) // 容器内目标路径
+ .withDirChildrenOnly(true) // 只复制目录内容,不含目录本身
+ .exec();
+
return id; // 返回容器 ID
}
diff --git a/src/main/resources/testCode.multiargs/Main.java b/src/main/resources/testCode.multiargs/Main.java
index 7e2c50f..008d7be 100644
--- a/src/main/resources/testCode.multiargs/Main.java
+++ b/src/main/resources/testCode.multiargs/Main.java
@@ -3,13 +3,6 @@ import java.util.List;
public class Main {
public static void main(String[] args) {
- List holder = new ArrayList<>();
- int mb = 1024 * 1024;
- long i = 0;
- while (true) {
- holder.add(new byte[10 * mb]);
- i++;
- System.out.println("allocated ~" + (i * 10) + "MB");
- }
+ System.out.println("helloworld ");
}
}
\ No newline at end of file
diff --git a/src/test/java/cn/meowrain/aioj/DockerCodeSandboxTest.java b/src/test/java/cn/meowrain/aioj/DockerCodeSandboxTest.java
new file mode 100644
index 0000000..cc67132
--- /dev/null
+++ b/src/test/java/cn/meowrain/aioj/DockerCodeSandboxTest.java
@@ -0,0 +1,208 @@
+package cn.meowrain.aioj;
+
+import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
+import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DockerCodeSandboxTest {
+
+ private DockerCodeSandbox sandbox;
+
+ @BeforeEach
+ void setUp() {
+ sandbox = new DockerCodeSandbox();
+ }
+
+ // ==================== Java ====================
+
+ @Test
+ void testJava_HelloWorld() {
+ String code = """
+ public class Main {
+ public static void main(String[] args) {
+ System.out.println("hello java");
+ }
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("java", code, List.of(""));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals("hello java", resp.getOutputList().get(0));
+ }
+
+ @Test
+ void testJava_ReadStdin() {
+ String code = """
+ import java.util.Scanner;
+ public class Main {
+ public static void main(String[] args) {
+ Scanner sc = new Scanner(System.in);
+ int a = sc.nextInt(), b = sc.nextInt();
+ System.out.println(a + b);
+ }
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("java", code, Arrays.asList("1 2", "10 20"));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals(List.of("3", "30"), resp.getOutputList());
+ }
+
+ @Test
+ void testJava_CompileError() {
+ String code = """
+ public class Main {
+ public static void main(String[] args) {
+ System.out.println("missing quote)
+ }
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("java", code, List.of(""));
+ assertEquals(2, resp.getStatus(), "should be compile error");
+ }
+
+ // ==================== Python ====================
+
+ @Test
+ void testPython_HelloWorld() {
+ String code = """
+ print("hello python")
+ """;
+ ExecuteCodeResponseDTO resp = execute("python", code, List.of(""));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals("hello python", resp.getOutputList().get(0));
+ }
+
+ @Test
+ void testPython_ReadStdin() {
+ String code = """
+ a, b = map(int, input().split())
+ print(a + b)
+ """;
+ ExecuteCodeResponseDTO resp = execute("python", code, Arrays.asList("3 4", "100 200"));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals(List.of("7", "300"), resp.getOutputList());
+ }
+
+ @Test
+ void testPython_RuntimeError() {
+ String code = """
+ print(1 / 0)
+ """;
+ ExecuteCodeResponseDTO resp = execute("python", code, List.of(""));
+ assertEquals(3, resp.getStatus(), "should be runtime error");
+ }
+
+ // ==================== Go ====================
+
+ @Test
+ void testGo_HelloWorld() {
+ String code = """
+ package main
+ import "fmt"
+ func main() {
+ fmt.Println("hello go")
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("go", code, List.of(""));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals("hello go", resp.getOutputList().get(0));
+ }
+
+ @Test
+ void testGo_ReadStdin() {
+ String code = """
+ package main
+ import "fmt"
+ func main() {
+ var a, b int
+ fmt.Scan(&a, &b)
+ fmt.Println(a + b)
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("go", code, Arrays.asList("5 6", "50 60"));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals(List.of("11", "110"), resp.getOutputList());
+ }
+
+ @Test
+ void testGo_CompileError() {
+ String code = """
+ package main
+ func main() {
+ x := 1
+ }
+ """;
+ ExecuteCodeResponseDTO resp = execute("go", code, List.of(""));
+ assertEquals(2, resp.getStatus(), "should be compile error (unused variable)");
+ }
+
+ // ==================== JavaScript ====================
+
+ @Test
+ void testJavaScript_HelloWorld() {
+ String code = """
+ console.log("hello js");
+ """;
+ ExecuteCodeResponseDTO resp = execute("javascript", code, List.of(""));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals("hello js", resp.getOutputList().get(0));
+ }
+
+ @Test
+ void testJavaScript_ReadStdin() {
+ String code = """
+ const readline = require('readline');
+ const rl = readline.createInterface({ input: process.stdin });
+ rl.on('line', (line) => {
+ const [a, b] = line.split(' ').map(Number);
+ console.log(a + b);
+ rl.close();
+ });
+ """;
+ ExecuteCodeResponseDTO resp = execute("javascript", code, Arrays.asList("7 8", "70 80"));
+ assertEquals(1, resp.getStatus(), resp.getMessage());
+ assertEquals(List.of("15", "150"), resp.getOutputList());
+ }
+
+ @Test
+ void testJavaScript_RuntimeError() {
+ String code = """
+ undefined.foo();
+ """;
+ ExecuteCodeResponseDTO resp = execute("javascript", code, List.of(""));
+ assertEquals(3, resp.getStatus(), "should be runtime error");
+ }
+
+ // ==================== 边界场景 ====================
+
+ @Test
+ void testUnsupportedLanguage() {
+ ExecuteCodeResponseDTO resp = execute("rust", "fn main() {}", List.of(""));
+ assertEquals(3, resp.getStatus());
+ assertTrue(resp.getMessage().contains("不支持的语言"));
+ }
+
+ // ==================== 辅助方法 ====================
+
+ private ExecuteCodeResponseDTO execute(String language, String code, List inputList) {
+ ExecuteCodeRequestDTO request = ExecuteCodeRequestDTO.builder()
+ .language(language)
+ .code(code)
+ .inputList(inputList)
+ .build();
+ ExecuteCodeResponseDTO resp = sandbox.executeCode(request);
+ System.out.printf("[%s] status=%d, message=%s, output=%s%n",
+ language, resp.getStatus(), resp.getMessage(), resp.getOutputList());
+ if (resp.getJudgeInfo() != null) {
+ System.out.printf(" -> time=%dms, memory=%dKB%n",
+ resp.getJudgeInfo().getTime(), resp.getJudgeInfo().getMemory());
+ }
+ return resp;
+ }
+}