提交代码
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"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\")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
.idea/CoolRequestCommonStatePersistent.xml
generated
Normal file
6
.idea/CoolRequestCommonStatePersistent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CoolRequestCommonStatePersistent">
|
||||||
|
<option name="searchCache" value="filter" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
15
.idea/deployment.xml
generated
Normal file
15
.idea/deployment.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PublishConfigData" autoUpload="Always" serverName="vmware" remoteFilesAllowedToDisappearOnAutoupload="false">
|
||||||
|
<serverData>
|
||||||
|
<paths name="vmware">
|
||||||
|
<serverdata>
|
||||||
|
<mappings>
|
||||||
|
<mapping deploy="/" local="$PROJECT_DIR$" web="/" />
|
||||||
|
</mappings>
|
||||||
|
</serverdata>
|
||||||
|
</paths>
|
||||||
|
</serverData>
|
||||||
|
<option name="myAutoUpload" value="ALWAYS" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/webServers.xml
generated
Normal file
14
.idea/webServers.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WebServers">
|
||||||
|
<option name="servers">
|
||||||
|
<webServer id="a14712e8-ebb3-463f-86fe-e45b23beb955" name="vmware">
|
||||||
|
<fileTransfer rootFolder="/home/meowrain/projects" accessType="SFTP" host="192.168.42.129" port="22" sshConfigId="fd54d56e-f791-498f-a842-bea367fa42f7" sshConfig="meowrain@192.168.42.129:22 password">
|
||||||
|
<advancedOptions>
|
||||||
|
<advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
|
||||||
|
</advancedOptions>
|
||||||
|
</fileTransfer>
|
||||||
|
</webServer>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
pom.xml
7
pom.xml
@@ -42,5 +42,12 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.11.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -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.InspectContainerResponse; // 容器检查响应
|
||||||
import com.github.dockerjava.api.command.InspectExecResponse; // exec 检查响应
|
import com.github.dockerjava.api.command.InspectExecResponse; // exec 检查响应
|
||||||
import com.github.dockerjava.api.command.PullImageResultCallback; // 拉取镜像回调
|
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.api.model.*; // Docker 模型类
|
||||||
import com.github.dockerjava.core.DefaultDockerClientConfig; // Docker 客户端默认配置
|
import com.github.dockerjava.core.DefaultDockerClientConfig; // Docker 客户端默认配置
|
||||||
import com.github.dockerjava.core.DockerClientConfig; // Docker 客户端配置接口
|
import com.github.dockerjava.core.DockerClientConfig; // Docker 客户端配置接口
|
||||||
@@ -108,10 +109,17 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
|
|||||||
@Override
|
@Override
|
||||||
protected RunResult runCase(String input, String codePath, LanguageConfig config,
|
protected RunResult runCase(String input, String codePath, LanguageConfig config,
|
||||||
long timeLimitMs, long memoryLimitKB) throws Exception {
|
long timeLimitMs, long memoryLimitKB) throws Exception {
|
||||||
// 将测试输入写入 input.txt(容器内通过 cat 读取)
|
// 将测试输入写入 input.txt
|
||||||
String inputFilePath = codePath + File.separator + "input.txt"; // input.txt 宿主机路径
|
String inputFilePath = codePath + File.separator + "input.txt"; // input.txt 宿主机路径
|
||||||
FileUtil.writeString(input != null ? input : "", inputFilePath, StandardCharsets.UTF_8); // 写入输入内容
|
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)
|
// 计算 JVM 堆内存限制(容器内存的 80%,最低 16MB)
|
||||||
long xmxMB = Math.max(16, (memoryLimitKB / 1024) * 80 / 100);
|
long xmxMB = Math.max(16, (memoryLimitKB / 1024) * 80 / 100);
|
||||||
String[] runCmd = config.getRunCmd(xmxMB); // 获取运行命令(替换内存占位符)
|
String[] runCmd = config.getRunCmd(xmxMB); // 获取运行命令(替换内存占位符)
|
||||||
@@ -168,10 +176,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
|
|||||||
synchronized (DockerCodeSandbox.class) { // 加锁防止并发拉取
|
synchronized (DockerCodeSandbox.class) { // 加锁防止并发拉取
|
||||||
if (readyImages.contains(image)) return; // 双重检查
|
if (readyImages.contains(image)) return; // 双重检查
|
||||||
DockerClient client = getDockerClient(); // 获取客户端
|
DockerClient client = getDockerClient(); // 获取客户端
|
||||||
List<Image> images = client.listImagesCmd() // 查询本地镜像
|
boolean exists;
|
||||||
.withImageNameFilter(image) // 按名称过滤
|
try {
|
||||||
.exec(); // 执行查询
|
client.inspectImageCmd(image).exec(); // 精确查找镜像
|
||||||
if (images.isEmpty()) { // 本地不存在
|
exists = true;
|
||||||
|
} catch (NotFoundException e) { // 镜像不存在
|
||||||
|
exists = false;
|
||||||
|
}
|
||||||
|
if (!exists) { // 本地不存在
|
||||||
System.out.println("拉取镜像: " + image); // 打印提示
|
System.out.println("拉取镜像: " + image); // 打印提示
|
||||||
try {
|
try {
|
||||||
client.pullImageCmd(image) // 拉取镜像
|
client.pullImageCmd(image) // 拉取镜像
|
||||||
@@ -198,7 +210,6 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
|
|||||||
|
|
||||||
// 配置宿主机资源限制和安全选项
|
// 配置宿主机资源限制和安全选项
|
||||||
HostConfig hostConfig = HostConfig.newHostConfig()
|
HostConfig hostConfig = HostConfig.newHostConfig()
|
||||||
.withBinds(new Bind(hostCodePath, new Volume(CONTAINER_WORK_DIR))) // 挂载代码目录到 /app
|
|
||||||
.withMemory(memoryBytes) // 内存硬限制
|
.withMemory(memoryBytes) // 内存硬限制
|
||||||
.withMemorySwap(memoryBytes) // 禁用 swap
|
.withMemorySwap(memoryBytes) // 禁用 swap
|
||||||
.withCpuCount(1L) // 限制 1 个 CPU
|
.withCpuCount(1L) // 限制 1 个 CPU
|
||||||
@@ -219,6 +230,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
|
|||||||
|
|
||||||
String id = container.getId(); // 获取容器 ID
|
String id = container.getId(); // 获取容器 ID
|
||||||
client.startContainerCmd(id).exec(); // 启动容器
|
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
|
return id; // 返回容器 ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,6 @@ import java.util.List;
|
|||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
List<byte[]> holder = new ArrayList<>();
|
System.out.println("helloworld ");
|
||||||
int mb = 1024 * 1024;
|
|
||||||
long i = 0;
|
|
||||||
while (true) {
|
|
||||||
holder.add(new byte[10 * mb]);
|
|
||||||
i++;
|
|
||||||
System.out.println("allocated ~" + (i * 10) + "MB");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
208
src/test/java/cn/meowrain/aioj/DockerCodeSandboxTest.java
Normal file
208
src/test/java/cn/meowrain/aioj/DockerCodeSandboxTest.java
Normal file
@@ -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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user