提交代码
This commit is contained in:
@@ -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\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.11.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</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.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<Image> 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
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,6 @@ import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
List<byte[]> 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 ");
|
||||
}
|
||||
}
|
||||
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