add interactive judge and Reconstruct judge mode
This commit is contained in:
parent
994b05e8db
commit
67fe2efbb4
|
@ -10,7 +10,7 @@
|
|||
|
||||
## 一、前言
|
||||
|
||||
基于前后端分离,分布式架构的在线测评平台(hoj),前端使用vue,后端主要使用springboot,redis,mysql,nacos等技术,**支持HDU、POJ、Codeforces(包括GYM)的vjudge判题,同时适配手机端、电脑端浏览,拥有讨论区与站内消息系统,支持私有训练、公开训练(题单),还有完善的比赛功能(打星队伍、关注队伍、外榜)。**
|
||||
基于前后端分离,分布式架构的在线测评平台(hoj),前端使用vue,后端主要使用springboot,redis,mysql,nacos等技术,**支持HDU、POJ、Codeforces(包括GYM)的vjudge判题,同时适配手机端、电脑端浏览,拥有讨论区与站内消息系统,支持私有训练、公开训练(题单),还有完善的判题模式(普通测评、特殊测评、交互测评)和完善的比赛功能(打星队伍、关注队伍、外榜)。**
|
||||
|
||||
| 在线Demo | 在线文档 | Github&Gitee仓库地址 | QQ群 |
|
||||
| :--------------------------------: | :-------------------------------------------------------: | :----------------------------------------------------------: | :-------: |
|
||||
|
@ -98,6 +98,7 @@ docker ps # 查看当前运行的容器状态
|
|||
| 2021-10-06 | 美化比赛排行榜,增加对FPS题目导入的支持 | Himit_ZH |
|
||||
| 2021-12-09 | 美化比赛排行榜,增加外榜、打星队伍、关注队伍的支持 | Himit_ZH |
|
||||
| 2022-01-01 | 增加公开训练和公开训练(题单) | Himit_ZH |
|
||||
| 2022-01-04 | 增加交互判题、重构judgeserver的三种判题模式(普通、特殊、交互) | Himit_ZH |
|
||||
|
||||
## 五、部分截图
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ module.exports = context => config({
|
|||
collapsable: true,
|
||||
children: [
|
||||
'use/import-problem',
|
||||
'use/judge-mode',
|
||||
'use/testcase',
|
||||
'use/training',
|
||||
'use/contest',
|
||||
|
@ -97,8 +98,7 @@ module.exports = context => config({
|
|||
'use/notice-announcement',
|
||||
'use/discussion-admin',
|
||||
'use/update-fe',
|
||||
'use/close-free-cdn',
|
||||
'use/spj'
|
||||
'use/close-free-cdn'
|
||||
]
|
||||
},
|
||||
],
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -20,7 +20,7 @@ features:
|
|||
details: 判题使用 cgroup 隔离用户程序,网站权限控制完善
|
||||
- title: 多样化
|
||||
details: 独有自身判题服务,同时支持其它知名OJ题目的提交判题
|
||||
footer: MIT Licensed | Copyright © 2022.01.01 @Author Himit_ZH QQ Group:598587305
|
||||
footer: MIT Licensed | Copyright © 2022.01.04 @Author Himit_ZH QQ Group:598587305
|
||||
---
|
||||
|
||||
[![Java](https://img.shields.io/badge/Java-1.8-informational)](http://openjdk.java.net/)
|
||||
|
@ -32,7 +32,7 @@ footer: MIT Licensed | Copyright © 2022.01.01 @Author Himit_ZH QQ Group:598587
|
|||
[![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/)
|
||||
[![QQ Group 598587305](https://img.shields.io/badge/QQ%20Group-598587305-blue)](https://qm.qq.com/cgi-bin/qm/qr?k=WWGBZ5gfDiBZOcpNvM8xnZTfUq7BT4Rs&jump_from=webapi)
|
||||
|
||||
Hcode Online Judge (HOJ) : 基于前后端分离,分布式架构的在线测评平台(hoj),前端使用vue,后端主要使用springboot,redis,mysql,nacos等技术,**支持HDU、POJ、Codeforces(包括GYM)的vjudge判题,同时适配手机端、电脑端浏览,拥有讨论区与站内消息系统,支持私有训练、公开训练(题单),还有完善的比赛功能(打星队伍、关注队伍、外榜)。**
|
||||
Hcode Online Judge (HOJ) : 基于前后端分离,分布式架构的在线测评平台(hoj),前端使用vue,后端主要使用springboot,redis,mysql,nacos等技术,**支持HDU、POJ、Codeforces(包括GYM)的vjudge判题,同时适配手机端、电脑端浏览,拥有讨论区与站内消息系统,支持私有训练、公开训练(题单),还有完善的判题模式(普通测评、特殊测评、交互测评)和完善的比赛功能(打星队伍、关注队伍、外榜)。**
|
||||
|
||||
[Github 仓库](https://github.com/HimitZH/HOJ)
|
||||
[Gitee 仓库](https://gitee.com/himitzh0730/hoj)
|
||||
|
@ -48,3 +48,10 @@ Hcode Online Judge (HOJ) : 基于前后端分离,分布式架构的在线测
|
|||
QQ: [372347736](https://wpa.qq.com/msgrd?v=3&uin=372347736&site=qq&menu=yes)
|
||||
HOJ交流群: [598587305](https://qm.qq.com/cgi-bin/qm/qr?k=WWGBZ5gfDiBZOcpNvM8xnZTfUq7BT4Rs&jump_from=webapi)
|
||||
|
||||
## 支持我们
|
||||
|
||||
**如果您觉得hoj好用,想要支持一下开发者,项目的开发与维护需要资金,欢迎进行捐助。**
|
||||
|
||||
**可以微信扫下方二维码支持一波,不必在乎多少,尽自己能力支持即可。**
|
||||
|
||||
![wxpy](/docs/wxpay.png)
|
|
@ -28,7 +28,8 @@ HOJ,全称 Hcode Online Judge,是基于(springcloud+vue)前后端分离
|
|||
- 支持ACM、OI题目及比赛,比赛拥有外榜、打星队伍、关注队伍等功能
|
||||
- 拥有讨论区、题目讨论、比赛讨论、同时拥有站内消息系统
|
||||
- 支持私有训练、公开训练(题单)
|
||||
- 支持testlib的SPJ
|
||||
- 支持testlib的特殊判题
|
||||
- 支持交互判题
|
||||
- 多样:支持自身题目数据评测,也支持其它知名OJ(HDU、Codeforces、POJ)题目的爬取与提交
|
||||
:::
|
||||
|
||||
|
|
|
@ -44,55 +44,65 @@
|
|||
|
||||
```json
|
||||
{
|
||||
// 题目支持的语言如下,可多可少
|
||||
"languages": ["C", "C++", "Java", "Python3", "Python2", "Golang", "C#"],
|
||||
"samples": [
|
||||
{
|
||||
"input": "1.in",
|
||||
"output": "1.out",
|
||||
//"score": 10 // 如果是oi题目需要给测试点加得分
|
||||
},
|
||||
{
|
||||
"input": "2.in",
|
||||
"output": "2.out",
|
||||
//"score": 10 // 如果是oi题目需要给测试点加得分
|
||||
}
|
||||
],
|
||||
"tags": ["测试题","测试"], // 题目标签,一般不超过三个
|
||||
"problem": {
|
||||
"auth": 1, // 1 公开赛
|
||||
"author": "admin", // 题目上传的作者,请使用用户名
|
||||
"isRemote": false, // 均为非VJ题目,不用修改
|
||||
"problemId": "HOJ-1010", // 题目的展示id
|
||||
"description": "", // 题目的描述,支持markdown语法
|
||||
"source": "", // 题目来源
|
||||
"title": "", // 题目标题
|
||||
"type": 0, // 0为ACM题目,1为OI题目
|
||||
"timeLimit": 1000, // 时间限制 单位是ms
|
||||
"memoryLimit": 256, // 空间限制 单位是mb
|
||||
"input": "", // 题目的输入描述
|
||||
"output": "", // 题目的输出描述
|
||||
"difficulty": 0, // 题目难度,1为简单,2为中等,3为困难
|
||||
"examples": "", // 题目的题面样例,格式为<input>输入</input><output>输出</output><input>输入</input><output>输出</output>
|
||||
"ioScore": 100, // OI题目总得分,与测试点总分一致
|
||||
"codeShare": true, // 该题目是否允许用户共享其提交的代码
|
||||
"hint": "", // 题目提示
|
||||
"isRemoveEndBlank": true, // 评测数据的输出是否自动去掉行末空格
|
||||
"openCaseResult": true, // 是否允许用户看到各个评测点的结果
|
||||
// "spjLanguage:"C" // 特殊判题的程序代码语言
|
||||
// "spjCode":"" // 特殊判题的代码
|
||||
},
|
||||
"isSpj": false, // 是否为特殊判题
|
||||
"codeTemplates": [
|
||||
{
|
||||
"code": "", // 模板代码
|
||||
"language": "C" // 模板代码语言
|
||||
},
|
||||
{
|
||||
"code": "", // 模板代码
|
||||
"language": "C++"// 模板代码语言
|
||||
}
|
||||
]
|
||||
"judgeMode":"default", // 普通判题:default, 特殊判题:spj, 交互判题:interactive
|
||||
// 题目支持的语言如下,可多可少
|
||||
"languages": ["C", "C++", "Java", "Python3", "Python2", "Golang", "C#"],
|
||||
"samples": [
|
||||
{
|
||||
"input": "1.in",
|
||||
"output": "1.out",
|
||||
//"score": 10 // 如果是oi题目需要给测试点加得分
|
||||
},
|
||||
{
|
||||
"input": "2.in",
|
||||
"output": "2.out",
|
||||
//"score": 10 // 如果是oi题目需要给测试点加得分
|
||||
}
|
||||
],
|
||||
"tags": ["测试题","测试"], // 题目标签,一般不超过三个
|
||||
"problem": {
|
||||
"auth": 1, // 1 公开赛
|
||||
"author": "admin", // 题目上传的作者,请使用用户名
|
||||
"isRemote": false, // 均为非VJ题目,不用修改
|
||||
"problemId": "HOJ-1010", // 题目的展示id
|
||||
"description": "", // 题目的描述,支持markdown语法
|
||||
"source": "", // 题目来源
|
||||
"title": "", // 题目标题
|
||||
"type": 0, // 0为ACM题目,1为OI题目
|
||||
"timeLimit": 1000, // 时间限制 单位是ms
|
||||
"memoryLimit": 256, // 空间限制 单位是mb
|
||||
"input": "", // 题目的输入描述
|
||||
"output": "", // 题目的输出描述
|
||||
"difficulty": 0, // 题目难度,1为简单,2为中等,3为困难
|
||||
"examples": "", // 题目的题面样例,格式为<input>输入</input><output>输出</output><input>输入</input><output>输出</output>
|
||||
"ioScore": 100, // OI题目总得分,与测试点总分一致
|
||||
"codeShare": true, // 该题目是否允许用户共享其提交的代码
|
||||
"hint": "", // 题目提示
|
||||
"isRemoveEndBlank": true, // 评测数据的输出是否自动去掉行末空格
|
||||
"openCaseResult": true, // 是否允许用户看到各个评测点的结果
|
||||
// "spjLanguage:"C" // 特殊判题的程序代码语言
|
||||
// "spjCode":"" // 特殊判题的代码
|
||||
},
|
||||
"codeTemplates": [
|
||||
{
|
||||
"code": "", // 模板代码
|
||||
"language": "C" // 模板代码语言
|
||||
},
|
||||
{
|
||||
"code": "", // 模板代码
|
||||
"language": "C++"// 模板代码语言
|
||||
}
|
||||
],
|
||||
// 用户程序的额外库文件 key:文件名,value:文件内容,如果没有请去掉
|
||||
"userExtraFile":{
|
||||
"testlib.h":"code",
|
||||
"stdio.h":"..."
|
||||
},
|
||||
// 特殊或交互程序的额外库文件 key:文件名,value:文件内容,如果没有请去掉
|
||||
"judgeExtraFile":{
|
||||
"testlib.h":"code",
|
||||
"stdio.h":"..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
# 判题模式
|
||||
|
||||
### 一、普通判题
|
||||
|
||||
**普通模式是程序在线评测平台(OJ)通用的判题模式**,主要的实现逻辑步骤如下:
|
||||
|
||||
:::tip
|
||||
|
||||
1. 选手程序读取题目标准输入文件的数据
|
||||
|
||||
2. 判题机执行代码逻辑得到选手输出
|
||||
3. 再将选手输出与题目标准输出文件的数据进行对比,最终得到判题结果
|
||||
|
||||
:::
|
||||
|
||||
### 二、特殊判题
|
||||
|
||||
#### 1. 什么是特殊判题?
|
||||
|
||||
特殊判题(Special Judge)是指OJ将使用一个特定的程序来判断提交的程序的输出是不是正确的,而不是单纯地看提交的程序的输出是否和标准输出一模一样。
|
||||
|
||||
#### 2. 使用场景
|
||||
|
||||
一般使用Special Judge都是因为题目的答案不唯一,更具体一点说的话一般是两种情况:
|
||||
|
||||
:::tip
|
||||
|
||||
- 题目最终要求输出一个解决方案,而且这个解决方案可能不唯一。
|
||||
- 题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以,比如0.01。这种情况保留3位小数、4位小数等等都是可以的,而且多保留几位小数也没什么坏处。
|
||||
|
||||
:::
|
||||
|
||||
#### 3. 支持
|
||||
|
||||
HOJ支持testlib.h头文件的直接使用 具体使用文档请看[https://oi-wiki.org/tools/testlib/](https://oi-wiki.org/tools/testlib/)
|
||||
|
||||
#### 4. 例题
|
||||
|
||||
在创建题目的适合,选择开启特殊判题,编写特殊判题程序,然后编译通过便可。
|
||||
|
||||
> 后台对题目使用特殊判题时,请参考以下程序例子 判断精度
|
||||
|
||||
- 使用testlib.h来进行特殊判题
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "testlib.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char *args[]){
|
||||
/**
|
||||
inf: 输入文件流
|
||||
ouf: 选手输出流
|
||||
ans: 标准答案流
|
||||
**/
|
||||
registerTestlibCmd(argc, args);
|
||||
double pans = ouf.readDouble();
|
||||
double jans = ans.readDouble();
|
||||
if (fabs(pans - jans)<0.01)
|
||||
quitf(_ok, "The answer is correct.");
|
||||
else
|
||||
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
|
||||
// quitf(_pe, "The answer is presentation error."); // 格式错误
|
||||
// quitf(_fail, "The something wrong cause system error."); // 系统错误
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
- 读取文件进行特殊判题
|
||||
|
||||
```cpp
|
||||
#include<iostream>
|
||||
#include<cstdio>
|
||||
|
||||
#define AC 100
|
||||
#define PE 101
|
||||
#define WA 102
|
||||
#define ERROR 103
|
||||
|
||||
using namespace std;
|
||||
|
||||
void close_file(FILE *f){
|
||||
if(f != NULL){
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *args[]){
|
||||
/**
|
||||
args[1]:标准输入文件路径
|
||||
args[2]:选手输出文件路径
|
||||
args[3]:标准输出文件路径
|
||||
**/
|
||||
FILE *std_input_file = fopen(args[1], "r");
|
||||
FILE *user_output_file = fopen(args[2], "r");
|
||||
FILE *std_output_file = fopen(args[3], "r");
|
||||
|
||||
double std_out; // 标准输出
|
||||
fscanf(user_output_file, "%lf", &std_out);
|
||||
|
||||
double user_output;// 用户输出
|
||||
fscanf(std_output_file, "%lf", &user_output);
|
||||
|
||||
// 关闭文件流
|
||||
close_file(std_input_file);
|
||||
close_file(user_output_file);
|
||||
close_file(std_output_file);
|
||||
|
||||
if (fabs(user_output - std_out)<=1e-6)
|
||||
return AC;
|
||||
else
|
||||
return WA;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 三、交互判题
|
||||
|
||||
**交互题** 是需要选手程序与测评程序交互来完成任务的题目。一类常见的情形是,选手程序向测评程序发出询问,并得到其反馈。
|
||||
|
||||
交互方式主要有如两种:**STDIO 交互**和**Grader 交互**
|
||||
|
||||
:::tip
|
||||
|
||||
主要的交互逻辑:交互程序的标准输出通过交互通道写到选手程序标准输入,选手程序的标准输出通过交互通道写到交互程序的标准输入,两者需要刷新输出缓存
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
在 C/C++ 中,`fflush(stdout)` 和 `std::cout << std::flush` 可以实现这个操作(使用 `std::cout << std::endl` 换行时也会自动刷新缓冲区,但是 `std::cout << '\n'` 不会)
|
||||
|
||||
:::
|
||||
|
||||
#### 1. 标准交互题
|
||||
|
||||
**A+B问题**
|
||||
|
||||
*选手程序*
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
using namespace std;
|
||||
int main(){
|
||||
int a,b;
|
||||
cin >> a >> b;
|
||||
cout << a + b;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
*交互程序*(这里使用testlib来实现,但也可以自己读取文件实现)
|
||||
|
||||
```cpp
|
||||
#include "testlib.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
setName("Interactor A+B");
|
||||
registerInteraction(argc, argv);
|
||||
// 读取题目标准输入文件的数据
|
||||
int a = inf.readInt();
|
||||
int b = inf.readInt();
|
||||
// 往交互通道写数据,记得用endl刷新缓冲区
|
||||
cout << a << " " << b << endl;
|
||||
int ans;
|
||||
// 读取用户程序写入到交互通道的数据
|
||||
cin >> ans;
|
||||
if (a + b == ans){ // 判断结果
|
||||
quitf(_ok, "correct");
|
||||
}else{
|
||||
else quitf(_wa,"incorrect");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 函数交互题
|
||||
|
||||
:::info
|
||||
|
||||
主要的交互逻辑:
|
||||
|
||||
1. 用户调用提供的库文件里面的方法执行答题逻辑,最后得出指定结果;
|
||||
2. 交互测评程序根据选手调用交互库执行逻辑后得出的结果,来进行最终判断评测的结果。
|
||||
|
||||
:::
|
||||
|
||||
需要给选手的程序添加交互库,在后台的题目管理可以选择添加。
|
||||
|
||||
![函数交互题](https://img-blog.csdnimg.cn/e6f17df56f26488c895944713a80e9ed.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASGltaXRfWkg=,size_20,color_FFFFFF,t_70,g_se,x_16)
|
||||
|
||||
**交互库**:提供写好的方法给选手调用
|
||||
|
||||
```cpp
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
namespace interactive {
|
||||
static int n, m, cnt;
|
||||
static bool hasUsedGetN = false;
|
||||
static bool hasSubmitted = false;
|
||||
void RE() {
|
||||
puts("re");
|
||||
exit(0);
|
||||
}
|
||||
int getn() {
|
||||
if (hasUsedGetN) RE();
|
||||
cin >> n;
|
||||
hasUsedGetN = 1;
|
||||
m = rand() % n + 1;
|
||||
return n;
|
||||
}
|
||||
int query(int x) {
|
||||
if (!hasUsedGetN || hasSubmitted) RE();
|
||||
cnt++;
|
||||
if (cnt > 100000) RE();
|
||||
if (x == m)
|
||||
return (rand() % 10 <= 3);
|
||||
else
|
||||
return (rand() % 10 <= 4);
|
||||
}
|
||||
void submit(int x) {
|
||||
if (hasSubmitted) RE();
|
||||
if (x == m)
|
||||
puts("ok");
|
||||
else
|
||||
puts("wa");
|
||||
hasSubmitted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
using interactive::getn;
|
||||
using interactive::query;
|
||||
using interactive::submit;
|
||||
```
|
||||
|
||||
**用户程序**:调用库文件的方法进行答题
|
||||
|
||||
```cpp
|
||||
#include <bits/stdc++.h>
|
||||
#include "interactive.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
int n = getn();
|
||||
for(int i = 1; i <= n; i++)
|
||||
{
|
||||
for(int j = 0; j < 900; j++)
|
||||
{
|
||||
if(query(i)) arr[i]++;
|
||||
}
|
||||
}
|
||||
int min = 1000, ans = 0;
|
||||
for(int i = 1; i <= n; i++)
|
||||
{
|
||||
if(arr[i] < min)
|
||||
{
|
||||
min = arr[i];
|
||||
ans = i;
|
||||
}
|
||||
}
|
||||
submit(ans);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**交互测评程序**:根据选手调用交互库执行逻辑后得出的结果,来进行最终判断评测的结果
|
||||
|
||||
```cpp
|
||||
#include "testlib.h"
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
registerTestlibCmd(argc, argv);
|
||||
// 读取选手最终输出文件的数据来判断结果
|
||||
std::string s = ouf.readToken();
|
||||
if (s == "ok")
|
||||
quitf(_ok, "Correct");
|
||||
else
|
||||
quitf(_wa, "Wrong Answer");
|
||||
}
|
||||
```
|
|
@ -1,98 +0,0 @@
|
|||
# 特殊判题
|
||||
|
||||
## 什么是特殊判题?
|
||||
|
||||
特殊判题(Special Judge)是指OJ将使用一个特定的程序来判断提交的程序的输出是不是正确的,而不是单纯地看提交的程序的输出是否和标准输出一模一样。
|
||||
|
||||
## 使用场景
|
||||
|
||||
一般使用Special Judge都是因为题目的答案不唯一,更具体一点说的话一般是两种情况:
|
||||
|
||||
- 题目最终要求输出一个解决方案,而且这个解决方案可能不唯一。
|
||||
- 题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以,比如0.01。这种情况保留3位小数、4位小数等等都是可以的,而且多保留几位小数也没什么坏处。
|
||||
|
||||
## 支持
|
||||
|
||||
HOJ支持testlib.h头文件的直接使用 具体使用文档请看[https://oi-wiki.org/tools/testlib/](https://oi-wiki.org/tools/testlib/)
|
||||
|
||||
## 例子:
|
||||
|
||||
在创建题目的适合,选择开启特殊判题,编写特殊判题程序,然后编译通过便可。
|
||||
|
||||
> 后台对题目使用特殊判题时,请参考以下程序例子 判断精度
|
||||
|
||||
- 使用Testlib特殊判题
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "testlib.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char *args[]){
|
||||
/**
|
||||
inf: 输入文件流
|
||||
ouf: 选手输出流
|
||||
ans: 标准答案流
|
||||
**/
|
||||
registerTestlibCmd(argc, args);
|
||||
double pans = ouf.readDouble();
|
||||
double jans = ans.readDouble();
|
||||
if (fabs(pans - jans)<0.01)
|
||||
quitf(_ok, "The answer is correct.");
|
||||
else
|
||||
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
|
||||
// quitf(_pe, "The answer is presentation error."); // 格式错误
|
||||
// quitf(_fail, "The something wrong cause system error."); // 系统错误
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
- 读取文件进行特殊判题
|
||||
|
||||
```cpp
|
||||
#include<iostream>
|
||||
#include<cstdio>
|
||||
|
||||
#define AC 100
|
||||
#define PE 101
|
||||
#define WA 102
|
||||
#define ERROR 103
|
||||
|
||||
using namespace std;
|
||||
|
||||
void close_file(FILE *f){
|
||||
if(f != NULL){
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *args[]){
|
||||
/**
|
||||
args[1]:标准输入文件路径
|
||||
args[2]:选手输出文件路径
|
||||
args[3]:标准输出文件路径
|
||||
**/
|
||||
FILE *std_input_file = fopen(args[1], "r");
|
||||
FILE *user_output_file = fopen(args[2], "r");
|
||||
FILE *std_output_file = fopen(args[3], "r");
|
||||
|
||||
double std_out; // 标准输出
|
||||
fscanf(user_output_file, "%lf", &std_out);
|
||||
|
||||
double user_output;// 用户输出
|
||||
fscanf(std_output_file, "%lf", &user_output);
|
||||
|
||||
// 关闭文件流
|
||||
close_file(std_input_file);
|
||||
close_file(user_output_file);
|
||||
close_file(std_output_file);
|
||||
|
||||
if (fabs(user_output - std_out)<=1e-6)
|
||||
return AC;
|
||||
else
|
||||
return WA;
|
||||
}
|
||||
```
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package top.hcode.hoj.controller.admin;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
@ -19,7 +21,7 @@ import top.hcode.hoj.common.result.CommonResult;
|
|||
import top.hcode.hoj.crawler.problem.ProblemStrategy;
|
||||
import top.hcode.hoj.judge.Dispatcher;
|
||||
import top.hcode.hoj.pojo.dto.ProblemDto;
|
||||
import top.hcode.hoj.pojo.entity.judge.CompileSpj;
|
||||
import top.hcode.hoj.pojo.entity.judge.CompileDTO;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
import top.hcode.hoj.pojo.entity.problem.ProblemCase;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
|
@ -107,7 +109,7 @@ public class AdminProblemController {
|
|||
@GetMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblem(@Valid @RequestParam("pid") Long pid) {
|
||||
public CommonResult getProblem(@RequestParam("pid") Long pid) {
|
||||
|
||||
Problem problem = problemService.getById(pid);
|
||||
|
||||
|
@ -204,15 +206,29 @@ public class AdminProblemController {
|
|||
@PostMapping("/compile-spj")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
|
||||
public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {
|
||||
|
||||
if (StringUtils.isEmpty(compileSpj.getSpjSrc()) ||
|
||||
StringUtils.isEmpty(compileSpj.getSpjLanguage())) {
|
||||
if (StringUtils.isEmpty(compileDTO.getCode()) ||
|
||||
StringUtils.isEmpty(compileDTO.getLanguage())) {
|
||||
return CommonResult.errorResponse("参数不能为空!");
|
||||
}
|
||||
|
||||
compileSpj.setToken(judgeToken);
|
||||
return dispatcher.dispatcher("compile", "/compile-spj", compileSpj);
|
||||
compileDTO.setToken(judgeToken);
|
||||
return dispatcher.dispatcher("compile", "/compile-spj", compileDTO);
|
||||
}
|
||||
|
||||
@PostMapping("/compile-interactive")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {
|
||||
|
||||
if (StringUtils.isEmpty(compileDTO.getCode()) ||
|
||||
StringUtils.isEmpty(compileDTO.getLanguage())) {
|
||||
return CommonResult.errorResponse("参数不能为空!");
|
||||
}
|
||||
|
||||
compileDTO.setToken(judgeToken);
|
||||
return dispatcher.dispatcher("compile", "/compile-interactive", compileDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/import-remote-oj-problem")
|
||||
|
|
|
@ -208,8 +208,19 @@ public class ImportAndExportProblemController {
|
|||
problemCaseList.add(BeanUtil.mapToBean(tmp, ProblemCase.class, true));
|
||||
}
|
||||
|
||||
// 格式化用户额外文件和判题额外文件
|
||||
if (importProblemVo.getUserExtraFile() != null) {
|
||||
JSONObject userExtraFileJson = JSONUtil.parseObj(importProblemVo.getUserExtraFile());
|
||||
problem.setUserExtraFile(userExtraFileJson.toString());
|
||||
}
|
||||
if (importProblemVo.getJudgeExtraFile() != null) {
|
||||
JSONObject judgeExtraFileJson = JSONUtil.parseObj(importProblemVo.getJudgeExtraFile());
|
||||
problem.setJudgeExtraFile(judgeExtraFileJson.toString());
|
||||
}
|
||||
|
||||
|
||||
ProblemDto problemDto = new ProblemDto();
|
||||
problemDto.setIsSpj(importProblemVo.getIsSpj())
|
||||
problemDto.setJudgeMode(importProblemVo.getJudgeMode())
|
||||
.setProblem(problem)
|
||||
.setCodeTemplates(codeTemplates)
|
||||
.setTags(tags)
|
||||
|
@ -227,7 +238,6 @@ public class ImportAndExportProblemController {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param pidList
|
||||
* @param response
|
||||
|
|
|
@ -253,14 +253,17 @@ public class ImportFpsController {
|
|||
problemSamples.add(new ProblemCase()
|
||||
.setInput(infileName).setOutput(outfileName));
|
||||
}
|
||||
|
||||
String mode = Constants.JudgeMode.DEFAULT.getMode();
|
||||
if (problem.getSpjLanguage() != null) {
|
||||
mode = Constants.JudgeMode.SPJ.getMode();
|
||||
}
|
||||
ProblemDto problemDto = new ProblemDto();
|
||||
problemDto.setSamples(problemSamples)
|
||||
.setIsUploadTestCase(true)
|
||||
.setUploadTestcaseDir(problemTestCaseDir)
|
||||
.setLanguages(languageList)
|
||||
.setTags(null)
|
||||
.setIsSpj(problem.getSpjLanguage() != null)
|
||||
.setJudgeMode(mode)
|
||||
.setProblem(problem)
|
||||
.setCodeTemplates(codeTemplates);
|
||||
|
||||
|
|
|
@ -190,7 +190,13 @@ public class ImportQDUOJController {
|
|||
problem.setAuthor(userRolesVo.getUsername());
|
||||
}
|
||||
ProblemDto problemDto = new ProblemDto();
|
||||
problemDto.setIsSpj(qdojProblemDto.getIsSpj())
|
||||
|
||||
String mode = Constants.JudgeMode.DEFAULT.getMode();
|
||||
if (qdojProblemDto.getIsSpj()){
|
||||
mode = Constants.JudgeMode.SPJ.getMode();
|
||||
}
|
||||
|
||||
problemDto.setJudgeMode(mode)
|
||||
.setProblem(problem)
|
||||
.setCodeTemplates(qdojProblemDto.getCodeTemplates())
|
||||
.setTags(tags)
|
||||
|
|
|
@ -16,7 +16,6 @@ import top.hcode.hoj.service.judge.impl.RemoteJudgeAccountServiceImpl;
|
|||
import top.hcode.hoj.utils.Constants;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -56,8 +55,8 @@ public class Dispatcher {
|
|||
toJudge(path, judgeData, judgeData.getJudge().getSubmitId(), judgeData.getRemoteJudgeProblem() != null);
|
||||
break;
|
||||
case "compile":
|
||||
CompileSpj compileSpj = (CompileSpj) data;
|
||||
return toCompile(path, compileSpj);
|
||||
CompileDTO compileDTO = (CompileDTO) data;
|
||||
return toCompile(path, compileDTO);
|
||||
default:
|
||||
throw new IllegalArgumentException("判题机不支持此调用类型");
|
||||
}
|
||||
|
@ -141,7 +140,7 @@ public class Dispatcher {
|
|||
}
|
||||
|
||||
|
||||
public CommonResult toCompile(String path, CompileSpj data) {
|
||||
public CommonResult toCompile(String path, CompileDTO data) {
|
||||
CommonResult result = CommonResult.errorResponse("没有可用的判题服务器,请重新尝试!");
|
||||
JudgeServer judgeServer = chooseUtils.chooseServer(false);
|
||||
if (judgeServer != null) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import lombok.Data;
|
|||
import lombok.experimental.Accessors;
|
||||
import top.hcode.hoj.pojo.entity.problem.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
@ -24,9 +25,9 @@ public class ProblemDto {
|
|||
|
||||
private String uploadTestcaseDir;
|
||||
|
||||
private Boolean isSpj;
|
||||
private String judgeMode;
|
||||
|
||||
private Boolean changeSpj;
|
||||
private Boolean changeModeCode;
|
||||
|
||||
private List<Language> languages;
|
||||
|
||||
|
|
|
@ -27,7 +27,11 @@ public class ImportProblemVo implements Serializable {
|
|||
|
||||
private List<HashMap<String,String>> codeTemplates;
|
||||
|
||||
private Boolean isSpj;
|
||||
private HashMap<String,String> userExtraFile;
|
||||
|
||||
private HashMap<String,String> judgeExtraFile;
|
||||
|
||||
private String judgeMode;
|
||||
|
||||
public Map<String, Object> getProblem() {
|
||||
return problem;
|
||||
|
@ -69,11 +73,27 @@ public class ImportProblemVo implements Serializable {
|
|||
this.codeTemplates = codeTemplates;
|
||||
}
|
||||
|
||||
public Boolean getIsSpj() {
|
||||
return isSpj;
|
||||
public String getJudgeMode() {
|
||||
return judgeMode;
|
||||
}
|
||||
|
||||
public void setIsSpj(Boolean spj) {
|
||||
isSpj = spj;
|
||||
public void setJudgeMode(String judgeMode) {
|
||||
this.judgeMode = judgeMode;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getUserExtraFile() {
|
||||
return userExtraFile;
|
||||
}
|
||||
|
||||
public void setUserExtraFile(HashMap<String, String> userExtraFile) {
|
||||
this.userExtraFile = userExtraFile;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getJudgeExtraFile() {
|
||||
return judgeExtraFile;
|
||||
}
|
||||
|
||||
public void setJudgeExtraFile(HashMap<String, String> judgeExtraFile) {
|
||||
this.judgeExtraFile = judgeExtraFile;
|
||||
}
|
||||
}
|
|
@ -82,7 +82,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
public boolean adminUpdateProblem(ProblemDto problemDto) {
|
||||
|
||||
Problem problem = problemDto.getProblem();
|
||||
if (!problemDto.getIsSpj()) {
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
|
||||
problem.setSpjLanguage(null).setSpjCode(null);
|
||||
}
|
||||
|
||||
|
@ -306,19 +306,19 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
// 如果是选择上传测试文件的,则需要遍历对应文件夹,读取数据,写入数据库,先前的题目数据一并清空。
|
||||
if (problemDto.getIsUploadTestCase()) {
|
||||
// 获取代理bean对象执行异步方法===》根据测试文件初始info
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(), caseVersion, pid, testcaseDir, problemDto.getSamples());
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(), caseVersion, pid, testcaseDir, problemDto.getSamples());
|
||||
} else {
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
}
|
||||
}
|
||||
// 变化成spj或者取消 同时更新测试数据
|
||||
else if (problemDto.getChangeSpj() != null && problemDto.getChangeSpj()) {
|
||||
// 变化成spj或interactive或者取消 同时更新测试数据
|
||||
else if (problemDto.getChangeModeCode() != null && problemDto.getChangeModeCode()) {
|
||||
problem.setCaseVersion(caseVersion);
|
||||
if (problemDto.getIsUploadTestCase()) {
|
||||
// 获取代理bean对象执行异步方法===》根据测试文件初始info
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(), caseVersion, pid, null, problemDto.getSamples());
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(), caseVersion, pid, null, problemDto.getSamples());
|
||||
} else {
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -341,8 +341,9 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
|
||||
Problem problem = problemDto.getProblem();
|
||||
|
||||
if (!problemDto.getIsSpj()) {
|
||||
problem.setSpjLanguage(null).setSpjCode(null);
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
|
||||
problem.setSpjLanguage(null)
|
||||
.setSpjCode(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -401,7 +402,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
}
|
||||
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemCases);
|
||||
// 获取代理bean对象执行异步方法===》根据测试文件初始info
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(),
|
||||
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(),
|
||||
problem.getCaseVersion(), pid, testcaseDir, problemDto.getSamples());
|
||||
} else {
|
||||
// oi题目需要求取平均值,给每个测试点初始oi的score值,默认总分100分
|
||||
|
@ -414,7 +415,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid)); // 设置好新题目的pid
|
||||
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
|
||||
}
|
||||
initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
|
||||
}
|
||||
|
||||
// 为新的题目添加对应的tag,可能tag是原表已有,也可能是新的,所以需要判断。
|
||||
|
@ -449,7 +450,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
|
||||
// 初始化上传文件的测试数据,写成json文件
|
||||
@Async
|
||||
public void initUploadTestCase(Boolean isSpj,
|
||||
public void initUploadTestCase(String mode,
|
||||
String version,
|
||||
Long problemId,
|
||||
String tmpTestcaseDir,
|
||||
|
@ -464,7 +465,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
}
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("isSpj", isSpj);
|
||||
result.set("mode", mode);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", problemCaseList.size());
|
||||
|
||||
|
@ -488,8 +489,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
FileReader outputFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(), CharsetUtil.UTF_8);
|
||||
String output = outputFile.readString().replaceAll("\r\n", "\n");
|
||||
|
||||
// spj是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (!isSpj) {
|
||||
// spj和interactive是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
|
||||
// 原数据MD5
|
||||
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(output.getBytes()));
|
||||
// 原数据大小
|
||||
|
@ -515,13 +516,13 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
|
||||
// 初始化手动输入上传的测试数据,写成json文件
|
||||
@Async
|
||||
public void initHandTestCase(Boolean isSpj,
|
||||
public void initHandTestCase(String mode,
|
||||
String version,
|
||||
Long problemId,
|
||||
List<ProblemCase> problemCaseList) {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("isSpj", isSpj);
|
||||
result.set("mode", mode);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", problemCaseList.size());
|
||||
|
||||
|
@ -548,8 +549,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
FileWriter outFile = new FileWriter(testCasesDir + "/" + outputName, CharsetUtil.UTF_8);
|
||||
outFile.write(outputData);
|
||||
|
||||
// spj是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (!isSpj) {
|
||||
// spj和interactive是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
|
||||
// 原数据MD5
|
||||
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(outputData.getBytes()));
|
||||
// 原数据大小
|
||||
|
@ -674,6 +675,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("All")
|
||||
public ImportProblemVo buildExportProblem(Long pid, List<HashMap<String, Object>> problemCaseList,
|
||||
HashMap<Long, String> languageMap, HashMap<Long, String> tagMap) {
|
||||
// 导出相当于导入
|
||||
|
@ -700,10 +702,19 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
codeTemplateList.add(tmp);
|
||||
}
|
||||
importProblemVo.setCodeTemplates(codeTemplateList);
|
||||
importProblemVo.setIsSpj(problem.getSpjCode() != null);
|
||||
|
||||
importProblemVo.setJudgeMode(problem.getJudgeMode());
|
||||
importProblemVo.setSamples(problemCaseList);
|
||||
|
||||
if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
|
||||
HashMap<String,String> userExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
|
||||
importProblemVo.setUserExtraFile(userExtraFileMap);
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
|
||||
HashMap<String,String> judgeExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
|
||||
importProblemVo.setUserExtraFile(judgeExtraFileMap);
|
||||
}
|
||||
|
||||
QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();
|
||||
problemTagQueryWrapper.eq("pid", pid);
|
||||
List<ProblemTag> problemTags = problemTagService.list(problemTagQueryWrapper);
|
||||
|
|
|
@ -278,4 +278,28 @@ public class Constants {
|
|||
}
|
||||
}
|
||||
|
||||
public enum JudgeMode {
|
||||
DEFAULT("default"),
|
||||
SPJ("spj"),
|
||||
INTERACTIVE("interactive");
|
||||
|
||||
private final String mode;
|
||||
|
||||
JudgeMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static JudgeMode getJudgeMode(String mode){
|
||||
for (JudgeMode judgeMode : JudgeMode.values()) {
|
||||
if (judgeMode.getMode().equals(mode)) {
|
||||
return judgeMode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,14 @@
|
|||
package top.hcode.hoj.controller;
|
||||
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.CommonResult;
|
||||
import top.hcode.hoj.common.exception.CompileError;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.SandboxRun;
|
||||
import top.hcode.hoj.pojo.entity.judge.CompileSpj;
|
||||
import top.hcode.hoj.pojo.entity.judge.CompileDTO;
|
||||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
import top.hcode.hoj.pojo.entity.judge.ToJudge;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
|
@ -22,8 +18,6 @@ import top.hcode.hoj.service.impl.*;
|
|||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -109,17 +103,30 @@ public class JudgeController {
|
|||
|
||||
|
||||
@PostMapping(value = "/compile-spj")
|
||||
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
|
||||
public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {
|
||||
|
||||
if (!compileSpj.getToken().equals(judgeToken)) {
|
||||
if (!compileDTO.getToken().equals(judgeToken)) {
|
||||
return CommonResult.errorResponse("对不起!您使用的判题服务调用凭证不正确!访问受限!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
try {
|
||||
judgeService.compileSpj(compileSpj.getSpjSrc(), compileSpj.getPid(), compileSpj.getSpjLanguage());
|
||||
judgeService.compileSpj(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(), compileDTO.getExtraFiles());
|
||||
return CommonResult.successResponse(null, "编译成功!");
|
||||
} catch (SystemError systemError) {
|
||||
return CommonResult.errorResponse(systemError.getStderr(), CommonResult.STATUS_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/compile-interactive")
|
||||
public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {
|
||||
|
||||
if (!compileDTO.getToken().equals(judgeToken)) {
|
||||
return CommonResult.errorResponse("对不起!您使用的判题服务调用凭证不正确!访问受限!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
try {
|
||||
judgeService.compileInteractive(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(), compileDTO.getExtraFiles());
|
||||
return CommonResult.successResponse(null, "编译成功!");
|
||||
} catch (CompileError compileError) {
|
||||
return CommonResult.errorResponse(compileError.getStderr(), CommonResult.STATUS_FAIL);
|
||||
} catch (SystemError systemError) {
|
||||
return CommonResult.errorResponse(systemError.getStderr(), CommonResult.STATUS_ERROR);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package top.hcode.hoj.judge;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.entity.JudgeDTO;
|
||||
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
|
||||
import top.hcode.hoj.judge.entity.SandBoxRes;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
import top.hcode.hoj.util.JudgeUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/2 20:46
|
||||
* @Description:
|
||||
*/
|
||||
public abstract class AbstractJudge {
|
||||
|
||||
protected static final int SPJ_AC = 100;
|
||||
|
||||
protected static final int SPJ_PE = 101;
|
||||
|
||||
protected static final int SPJ_WA = 102;
|
||||
|
||||
protected static final int SPJ_ERROR = 103;
|
||||
|
||||
public JSONObject judge(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
|
||||
JSONArray judgeResultList = judgeCase(judgeDTO, judgeGlobalDTO);
|
||||
|
||||
switch (judgeGlobalDTO.getJudgeMode()) {
|
||||
case SPJ:
|
||||
case DEFAULT:
|
||||
return process(judgeDTO, judgeGlobalDTO, judgeResultList);
|
||||
case INTERACTIVE:
|
||||
return processMultiple(judgeDTO, judgeGlobalDTO, judgeResultList);
|
||||
default:
|
||||
throw new RuntimeException("The problem mode is error:" + judgeGlobalDTO.getJudgeMode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError;
|
||||
|
||||
private JSONObject process(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList) throws SystemError {
|
||||
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
|
||||
SandBoxRes sandBoxRes = SandBoxRes.builder()
|
||||
.stdout(((JSONObject) judgeResult.get("files")).getStr("stdout"))
|
||||
.stderr(((JSONObject) judgeResult.get("files")).getStr("stderr"))
|
||||
.time(judgeResult.getLong("time") / 1000000) // ns->ms
|
||||
.memory(judgeResult.getLong("memory") / 1024) // b-->kb
|
||||
.exitCode(judgeResult.getInt("exitStatus"))
|
||||
.status(judgeResult.getInt("status"))
|
||||
.build();
|
||||
|
||||
return checkResult(sandBoxRes, judgeDTO, judgeGlobalDTO);
|
||||
}
|
||||
|
||||
private JSONObject processMultiple(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList) throws SystemError {
|
||||
|
||||
JSONObject userJudgeResult = (JSONObject) judgeResultList.get(0);
|
||||
SandBoxRes userSandBoxRes = SandBoxRes.builder()
|
||||
.stdout(((JSONObject) userJudgeResult.get("files")).getStr("stdout"))
|
||||
.stderr(((JSONObject) userJudgeResult.get("files")).getStr("stderr"))
|
||||
.time(userJudgeResult.getLong("time") / 1000000) // ns->ms
|
||||
.memory(userJudgeResult.getLong("memory") / 1024) // b-->kb
|
||||
.exitCode(userJudgeResult.getInt("exitStatus"))
|
||||
.status(userJudgeResult.getInt("status"))
|
||||
.build();
|
||||
|
||||
JSONObject interactiveJudgeResult = (JSONObject) judgeResultList.get(1);
|
||||
SandBoxRes interactiveSandBoxRes = SandBoxRes.builder()
|
||||
.stdout(((JSONObject) interactiveJudgeResult.get("files")).getStr("stdout"))
|
||||
.stderr(((JSONObject) interactiveJudgeResult.get("files")).getStr("stderr"))
|
||||
.time(interactiveJudgeResult.getLong("time") / 1000000) // ns->ms
|
||||
.memory(interactiveJudgeResult.getLong("memory") / 1024) // b-->kb
|
||||
.exitCode(interactiveJudgeResult.getInt("exitStatus"))
|
||||
.status(interactiveJudgeResult.getInt("status"))
|
||||
.build();
|
||||
|
||||
return checkMultipleResult(userSandBoxRes, interactiveSandBoxRes, judgeDTO, judgeGlobalDTO);
|
||||
}
|
||||
|
||||
public abstract JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError;
|
||||
|
||||
public abstract JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO);
|
||||
|
||||
protected List<String> parseRunCommand(String command,
|
||||
Constants.RunConfig runConfig,
|
||||
String testCaseInputName,
|
||||
String userOutputName,
|
||||
String testCaseOutputName) {
|
||||
|
||||
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),
|
||||
runConfig.getExeName(), Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseInputName,
|
||||
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + userOutputName,
|
||||
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseOutputName);
|
||||
|
||||
return JudgeUtils.translateCommandline(command);
|
||||
}
|
||||
|
||||
protected JSONObject parseTestLibErr(String err) {
|
||||
|
||||
JSONObject res = new JSONObject(2);
|
||||
|
||||
if (err.startsWith("ok ")) {
|
||||
res.set("code", SPJ_AC);
|
||||
res.set("errMsg", err.split("ok ")[1]);
|
||||
} else if (err.startsWith("wrong answer ")) {
|
||||
res.set("code", SPJ_WA);
|
||||
res.set("errMsg", err.split("wrong answer ")[1]);
|
||||
} else if (err.startsWith("wrong output format ")) {
|
||||
res.set("code", SPJ_WA);
|
||||
res.set("errMsg", "May be output presentation error. " + err.split("wrong output format")[1]);
|
||||
} else if (err.startsWith("FAIL ")) {
|
||||
res.set("code", SPJ_ERROR);
|
||||
res.set("errMsg", err.split("FAIL ")[1]);
|
||||
} else {
|
||||
res.set("code", SPJ_ERROR);
|
||||
res.set("errMsg", err);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ import top.hcode.hoj.common.exception.SystemError;
|
|||
import top.hcode.hoj.util.Constants;
|
||||
import top.hcode.hoj.util.JudgeUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -19,7 +21,8 @@ import java.util.List;
|
|||
* @Description: 判题流程解耦重构2.0,该类只负责编译
|
||||
*/
|
||||
public class Compiler {
|
||||
public static String compile(Constants.CompileConfig compileConfig, String code, String language) throws SystemError, CompileError, SubmitError {
|
||||
public static String compile(Constants.CompileConfig compileConfig, String code,
|
||||
String language, HashMap<String, String> extraFiles) throws SystemError, CompileError, SubmitError {
|
||||
|
||||
if (compileConfig == null) {
|
||||
throw new RuntimeException("Unsupported language " + language);
|
||||
|
@ -29,12 +32,13 @@ public class Compiler {
|
|||
JSONArray result = SandboxRun.compile(compileConfig.getMaxCpuTime(),
|
||||
compileConfig.getMaxRealTime(),
|
||||
compileConfig.getMaxMemory(),
|
||||
128 * 1024 * 1024L,
|
||||
256 * 1024 * 1024L,
|
||||
compileConfig.getSrcName(),
|
||||
compileConfig.getExeName(),
|
||||
parseCompileCommand(compileConfig.getCommand(), compileConfig),
|
||||
compileConfig.getEnvs(),
|
||||
code,
|
||||
extraFiles,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
|
@ -47,17 +51,17 @@ public class Compiler {
|
|||
|
||||
String fileId = ((JSONObject) compileResult.get("fileIds")).getStr(compileConfig.getExeName());
|
||||
if (StringUtils.isEmpty(fileId)) {
|
||||
throw new SubmitError("Executable file not found.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
|
||||
throw new SubmitError("Executable file not found.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
|
||||
((JSONObject) compileResult.get("files")).getStr("stderr"));
|
||||
}
|
||||
return fileId;
|
||||
}
|
||||
|
||||
public static Boolean compileSpj(String code, Long pid, String spjLanguage) throws SystemError {
|
||||
public static Boolean compileSpj(String code, Long pid, String language, HashMap<String, String> extraFiles) throws SystemError {
|
||||
|
||||
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + spjLanguage);
|
||||
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + language);
|
||||
if (spjCompiler == null) {
|
||||
throw new RuntimeException("Unsupported language " + spjLanguage);
|
||||
throw new RuntimeException("Unsupported SPJ language:" + language);
|
||||
}
|
||||
|
||||
boolean copyOutExe = true;
|
||||
|
@ -69,15 +73,16 @@ public class Compiler {
|
|||
JSONArray res = SandboxRun.compile(spjCompiler.getMaxCpuTime(),
|
||||
spjCompiler.getMaxRealTime(),
|
||||
spjCompiler.getMaxMemory(),
|
||||
128 * 1024 * 1024L,
|
||||
256 * 1024 * 1024L,
|
||||
spjCompiler.getSrcName(),
|
||||
spjCompiler.getExeName(),
|
||||
parseCompileCommand(spjCompiler.getCommand(), spjCompiler),
|
||||
spjCompiler.getEnvs(),
|
||||
code,
|
||||
extraFiles,
|
||||
false,
|
||||
copyOutExe,
|
||||
Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + "/" + pid
|
||||
Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + pid
|
||||
);
|
||||
JSONObject compileResult = (JSONObject) res.get(0);
|
||||
if (compileResult.getInt("status").intValue() != Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
|
@ -87,6 +92,42 @@ public class Compiler {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static Boolean compileInteractive(String code, Long pid, String language, HashMap<String, String> extraFiles) throws SystemError {
|
||||
|
||||
Constants.CompileConfig interactiveCompiler = Constants.CompileConfig.getCompilerByLanguage("INTERACTIVE-" + language);
|
||||
if (interactiveCompiler == null) {
|
||||
throw new RuntimeException("Unsupported interactive language:" + language);
|
||||
}
|
||||
|
||||
boolean copyOutExe = true;
|
||||
if (pid == null) { // 题目id为空,则不进行本地存储,可能为新建题目时测试特判程序是否正常的判断而已
|
||||
copyOutExe = false;
|
||||
}
|
||||
|
||||
// 调用安全沙箱对特别判题程序进行编译
|
||||
JSONArray res = SandboxRun.compile(interactiveCompiler.getMaxCpuTime(),
|
||||
interactiveCompiler.getMaxRealTime(),
|
||||
interactiveCompiler.getMaxMemory(),
|
||||
256 * 1024 * 1024L,
|
||||
interactiveCompiler.getSrcName(),
|
||||
interactiveCompiler.getExeName(),
|
||||
parseCompileCommand(interactiveCompiler.getCommand(), interactiveCompiler),
|
||||
interactiveCompiler.getEnvs(),
|
||||
code,
|
||||
extraFiles,
|
||||
false,
|
||||
copyOutExe,
|
||||
Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent() + File.separator + pid
|
||||
);
|
||||
JSONObject compileResult = (JSONObject) res.get(0);
|
||||
if (compileResult.getInt("status").intValue() != Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
throw new SystemError("Interactive Judge Code Compile Error.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
|
||||
((JSONObject) compileResult.get("files")).getStr("stderr"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<String> parseCompileCommand(String command, Constants.CompileConfig compileConfig) {
|
||||
|
||||
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),
|
||||
|
|
|
@ -1,95 +1,103 @@
|
|||
package top.hcode.hoj.judge;
|
||||
|
||||
import cn.hutool.core.io.file.FileWriter;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.entity.JudgeDTO;
|
||||
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
|
||||
import top.hcode.hoj.judge.task.DefaultJudge;
|
||||
import top.hcode.hoj.judge.task.InteractiveJudge;
|
||||
import top.hcode.hoj.judge.task.SpecialJudge;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
import top.hcode.hoj.util.JudgeUtils;
|
||||
import top.hcode.hoj.util.ThreadPoolUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/4/16 12:15
|
||||
* @Description: 判题流程解耦重构2.0,该类负责输入数据进入程序进行测评
|
||||
* @Description: 判题流程解耦重构3.0,该类负责输入数据进入程序进行测评
|
||||
*/
|
||||
@Slf4j(topic = "hoj")
|
||||
@Component
|
||||
public class JudgeRun {
|
||||
|
||||
@Resource
|
||||
private DefaultJudge defaultJudge;
|
||||
|
||||
private static final int SPJ_AC = 100;
|
||||
@Resource
|
||||
private SpecialJudge specialJudge;
|
||||
|
||||
private static final int SPJ_PE = 101;
|
||||
@Resource
|
||||
private InteractiveJudge interactiveJudge;
|
||||
|
||||
private static final int SPJ_WA = 102;
|
||||
|
||||
private static final int SPJ_ERROR = 103;
|
||||
|
||||
private Long submitId;
|
||||
|
||||
private Long problemId;
|
||||
|
||||
private String testCasesDir;
|
||||
|
||||
private JSONObject testCasesInfo;
|
||||
|
||||
private Constants.RunConfig runConfig;
|
||||
|
||||
private Constants.RunConfig spjRunConfig;
|
||||
|
||||
|
||||
public JudgeRun(Long submitId, Long problemId, String testCasesDir, JSONObject testCasesInfo, Constants.RunConfig runConfig, Constants.RunConfig spjRunConfig) {
|
||||
this.submitId = submitId;
|
||||
this.problemId = problemId;
|
||||
this.testCasesDir = testCasesDir;
|
||||
this.testCasesInfo = testCasesInfo;
|
||||
this.runConfig = runConfig;
|
||||
this.spjRunConfig = spjRunConfig;
|
||||
}
|
||||
|
||||
public List<JSONObject> judgeAllCase(String userFileId,
|
||||
Long maxTime,
|
||||
Long maxMemory,
|
||||
Integer maxStack,
|
||||
Boolean getUserOutput,
|
||||
Boolean isRemoveEOFBlank,
|
||||
String spjExeName)
|
||||
public List<JSONObject> judgeAllCase(Long submitId,
|
||||
Problem problem,
|
||||
String judgeLanguage,
|
||||
String testCasesDir,
|
||||
JSONObject testCasesInfo,
|
||||
String userFileId,
|
||||
Boolean getUserOutput)
|
||||
throws SystemError, ExecutionException, InterruptedException {
|
||||
|
||||
if (testCasesInfo == null) {
|
||||
throw new SystemError("The evaluation data of the problem does not exist", null, null);
|
||||
}
|
||||
|
||||
|
||||
List<FutureTask<JSONObject>> futureTasks = new ArrayList<>();
|
||||
JSONArray testcaseList = (JSONArray) testCasesInfo.get("testCases");
|
||||
Boolean isSpj = testCasesInfo.getBool("isSpj");
|
||||
|
||||
// 默认给题目限制时间+200ms用来测评
|
||||
final Long testTime = maxTime + 200;
|
||||
Long testTime = (long) problem.getTimeLimit() + 200;
|
||||
|
||||
Constants.JudgeMode judgeMode = Constants.JudgeMode.getJudgeMode(problem.getJudgeMode());
|
||||
|
||||
if (judgeMode == null) {
|
||||
throw new RuntimeException("The judge mode of problem " + problem.getProblemId() + " error:" + problem.getJudgeMode());
|
||||
}
|
||||
|
||||
// 用户输出的文件夹
|
||||
String runDir = Constants.JudgeDir.RUN_WORKPLACE_DIR.getContent() + File.separator + submitId;
|
||||
|
||||
Constants.RunConfig runConfig = Constants.RunConfig.getRunnerByLanguage(judgeLanguage);
|
||||
Constants.RunConfig spjConfig = Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage());
|
||||
Constants.RunConfig interactiveConfig = Constants.RunConfig.getRunnerByLanguage("INTERACTIVE-" + problem.getSpjLanguage());
|
||||
|
||||
JudgeGlobalDTO judgeGlobalDTO = JudgeGlobalDTO.builder()
|
||||
.problemId(problem.getId())
|
||||
.judgeMode(judgeMode)
|
||||
.userFileId(userFileId)
|
||||
.runDir(runDir)
|
||||
.testTime(testTime)
|
||||
.maxMemory(problem.getMemoryLimit() * 1024L)
|
||||
.maxTime((long) problem.getTimeLimit())
|
||||
.maxStack(problem.getStackLimit())
|
||||
.testCaseInfo(testCasesInfo)
|
||||
.judgeExtraFiles(JudgeUtils.getProblemExtraFileMap(problem, "judge"))
|
||||
.runConfig(runConfig)
|
||||
.spjRunConfig(spjConfig)
|
||||
.interactiveRunConfig(interactiveConfig)
|
||||
.needUserOutputFile(getUserOutput)
|
||||
.removeEOLBlank(problem.getIsRemoveEndBlank())
|
||||
.build();
|
||||
|
||||
for (int index = 0; index < testcaseList.size(); index++) {
|
||||
|
||||
JSONObject testcase = (JSONObject) testcaseList.get(index);
|
||||
|
||||
// 将每个需要测试的线程任务加入任务列表中
|
||||
final int testCaseId = index;
|
||||
final int testCaseId = index + 1;
|
||||
// 输入文件名
|
||||
final String inputFileName = testcase.getStr("inputName");
|
||||
// 输出文件名
|
||||
final String outputFileName = testcase.getStr("outputName");
|
||||
// 测试样例的路径
|
||||
// 题目数据的输入文件的路径
|
||||
final String testCaseInputPath = testCasesDir + File.separator + inputFileName;
|
||||
// 题目数据的输出文件的路径
|
||||
final String testCaseOutputPath = testCasesDir + File.separator + outputFileName;
|
||||
// 数据库表的测试样例id
|
||||
final Long caseId = testcase.getLong("caseId", null);
|
||||
// 该测试点的满分
|
||||
|
@ -97,52 +105,34 @@ public class JudgeRun {
|
|||
|
||||
final Long maxOutputSize = Math.max(testcase.getLong("outputSize", 0L) * 2, 16 * 1024 * 1024L);
|
||||
|
||||
if (!isSpj) {
|
||||
futureTasks.add(new FutureTask<>(new Callable<JSONObject>() {
|
||||
@Override
|
||||
public JSONObject call() throws SystemError {
|
||||
JSONObject result = judgeOneCase(userFileId,
|
||||
testCaseId,
|
||||
runDir,
|
||||
testCaseInputPath,
|
||||
testTime,// 默认给题目限制时间+200ms用来测评
|
||||
maxTime,
|
||||
maxMemory,
|
||||
maxStack,
|
||||
maxOutputSize,
|
||||
getUserOutput,
|
||||
isRemoveEOFBlank);
|
||||
result.set("caseId", caseId);
|
||||
result.set("score", score);
|
||||
result.set("inputFileName", inputFileName);
|
||||
result.set("outputFileName", outputFileName);
|
||||
return result;
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
final String testCaseOutputPath = testCasesDir + File.separator + outputFileName;
|
||||
futureTasks.add(new FutureTask<>(new Callable<JSONObject>() {
|
||||
@Override
|
||||
public JSONObject call() throws SystemError {
|
||||
JSONObject result = spjJudgeOneCase(userFileId,
|
||||
testCaseId,
|
||||
runDir,
|
||||
testCaseInputPath,
|
||||
testCaseOutputPath,
|
||||
testTime,// 默认给题目限制时间+200ms用来测评
|
||||
maxTime,
|
||||
maxMemory,
|
||||
maxOutputSize,
|
||||
maxStack,
|
||||
spjExeName);
|
||||
result.set("caseId", caseId);
|
||||
result.set("score", score);
|
||||
result.set("inputFileName", inputFileName);
|
||||
result.set("outputFileName", outputFileName);
|
||||
return result;
|
||||
}
|
||||
}));
|
||||
}
|
||||
JudgeDTO judgeDTO = JudgeDTO.builder()
|
||||
.testCaseId(testCaseId)
|
||||
.testCaseInputPath(testCaseInputPath)
|
||||
.testCaseOutputPath(testCaseOutputPath)
|
||||
.maxOutputSize(maxOutputSize)
|
||||
.build();
|
||||
|
||||
futureTasks.add(new FutureTask<>(() -> {
|
||||
JSONObject result;
|
||||
switch (judgeMode) {
|
||||
case DEFAULT:
|
||||
result = defaultJudge.judge(judgeDTO, judgeGlobalDTO);
|
||||
break;
|
||||
case SPJ:
|
||||
result = specialJudge.judge(judgeDTO, judgeGlobalDTO);
|
||||
break;
|
||||
case INTERACTIVE:
|
||||
result = interactiveJudge.judge(judgeDTO, judgeGlobalDTO);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("The problem mode is error:" + judgeMode);
|
||||
}
|
||||
result.set("caseId", caseId);
|
||||
result.set("score", score);
|
||||
result.set("inputFileName", inputFileName);
|
||||
result.set("outputFileName", outputFileName);
|
||||
return result;
|
||||
}));
|
||||
}
|
||||
|
||||
// 提交到线程池进行执行
|
||||
|
@ -169,464 +159,4 @@ public class JudgeRun {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
private JSONObject spjJudgeOneCase(String userFileId,
|
||||
Integer testCaseId,
|
||||
String runDir,
|
||||
String testCaseInputFilePath,
|
||||
String testCaseOutputFilePath,
|
||||
Long testTime,
|
||||
Long maxTime,
|
||||
Long maxMemory,
|
||||
Long maxOutputSize,
|
||||
Integer maxStack,
|
||||
String spjExeName) throws SystemError {
|
||||
|
||||
// 调用安全沙箱使用测试点对程序进行测试
|
||||
JSONArray judgeResultList = SandboxRun.testCase(
|
||||
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
testCaseInputFilePath,
|
||||
testTime,
|
||||
maxOutputSize,
|
||||
maxStack,
|
||||
runConfig.getExeName(),
|
||||
userFileId);
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
|
||||
|
||||
// 获取跑题用户输出或错误输出
|
||||
String userStdOut = ((JSONObject) judgeResult.get("files")).getStr("stdout");
|
||||
String userErrOut = ((JSONObject) judgeResult.get("files")).getStr("stderr");
|
||||
|
||||
StringBuilder errMsg = new StringBuilder();
|
||||
|
||||
// 获取程序运行内存 b-->kb
|
||||
long memory = judgeResult.getLong("memory") / 1024;
|
||||
// 获取程序运行时间 ns->ms
|
||||
long time = judgeResult.getLong("time") / 1000000;
|
||||
// 异常退出的状态码
|
||||
int exitCode = judgeResult.getInt("exitStatus");
|
||||
// 如果测试跑题无异常
|
||||
if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
|
||||
// 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
|
||||
if (time >= maxTime) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (memory >= maxMemory) {
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else {
|
||||
|
||||
// 对于当前测试样例,用户程序的输出对应生成的文件
|
||||
String userOutputFilePath = runDir + File.separator + (testCaseId + 1) + ".out";
|
||||
FileWriter stdWriter = new FileWriter(userOutputFilePath);
|
||||
stdWriter.write(userStdOut);
|
||||
|
||||
// 特判程序的路径
|
||||
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + problemId + File.separator + spjExeName;
|
||||
|
||||
String userOutputFileName = problemId + "_user_output";
|
||||
String testCaseInputFileName = problemId + "_input";
|
||||
String testCaseOutputFileName = problemId + "_output";
|
||||
// 进行spj程序运行比对
|
||||
JSONObject spjResult = spjCheckResult(userOutputFilePath,
|
||||
userOutputFileName,
|
||||
testCaseInputFilePath,
|
||||
testCaseInputFileName,
|
||||
testCaseOutputFilePath,
|
||||
testCaseOutputFileName,
|
||||
spjExeSrc,
|
||||
spjExeName,
|
||||
runDir);
|
||||
int code = spjResult.getInt("code");
|
||||
if (code == SPJ_WA) {
|
||||
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
|
||||
} else if (code == SPJ_AC) {
|
||||
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
|
||||
} else if (code == SPJ_PE) {
|
||||
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
|
||||
} else {
|
||||
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
|
||||
}
|
||||
|
||||
String spjErrMsg = spjResult.getStr("errMsg");
|
||||
if (!StringUtils.isEmpty(spjErrMsg)) {
|
||||
errMsg.append(spjErrMsg).append(" ");
|
||||
}
|
||||
|
||||
}
|
||||
} else if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus()) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (exitCode != 0) {
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
if (exitCode < 32) {
|
||||
errMsg.append(String.format("ExitCode: %s (%s)\n", exitCode, SandboxRun.signals.get(exitCode)));
|
||||
} else {
|
||||
errMsg.append(String.format("ExitCode: %s\n", exitCode));
|
||||
}
|
||||
} else {
|
||||
result.set("status", judgeResult.getInt("status"));
|
||||
}
|
||||
|
||||
// b
|
||||
result.set("memory", memory);
|
||||
// ns->ms
|
||||
result.set("time", time);
|
||||
|
||||
// 记录该测试点的错误信息
|
||||
if (!StringUtils.isEmpty(errMsg.toString())) {
|
||||
result.set("errMsg", errMsg.toString());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(userErrOut)) {
|
||||
// 同时记录错误信息
|
||||
errMsg.append(userErrOut);
|
||||
// 对于当前测试样例,用户的错误提示生成对应文件
|
||||
FileWriter errWriter = new FileWriter(runDir + File.separator + testCaseId + ".err");
|
||||
errWriter.write(userErrOut);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private JSONObject spjCheckResult(String userOutputFilePath,
|
||||
String userOutputFileName,
|
||||
String testCaseInputFilePath,
|
||||
String testCaseInputFileName,
|
||||
String testCaseOutputFilePath,
|
||||
String testCaseOutputFileName,
|
||||
String spjExeSrc,
|
||||
String spjExeName,
|
||||
String runDir) throws SystemError {
|
||||
|
||||
// 调用安全沙箱运行spj程序
|
||||
JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(
|
||||
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
|
||||
spjRunConfig.getEnvs(),
|
||||
userOutputFilePath,
|
||||
userOutputFileName,
|
||||
testCaseInputFilePath,
|
||||
testCaseInputFileName,
|
||||
testCaseOutputFilePath,
|
||||
testCaseOutputFileName,
|
||||
spjExeSrc,
|
||||
spjExeName);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);
|
||||
|
||||
// 获取跑题用户输出或错误输出
|
||||
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
|
||||
|
||||
if (!StringUtils.isEmpty(spjErrOut)) {
|
||||
result.set("errMsg", spjErrOut);
|
||||
}
|
||||
|
||||
// 退出状态码
|
||||
int exitCode = spjJudgeResult.getInt("exitStatus");
|
||||
// 如果测试跑题无异常
|
||||
if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
result.set("code", SPJ_AC);
|
||||
} else {
|
||||
result.set("code", exitCode);
|
||||
}
|
||||
} else if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_RUNTIME_ERROR.getStatus()) {
|
||||
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
|
||||
result.set("code", exitCode);
|
||||
} else {
|
||||
if (!StringUtils.isEmpty(spjErrOut)) {
|
||||
// 适配testlib.h 根据错误信息前缀判断
|
||||
return parseTestlibErr(spjErrOut);
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private JSONObject parseTestlibErr(String err) {
|
||||
|
||||
JSONObject res = new JSONObject(2);
|
||||
|
||||
if (err.startsWith("ok ")) {
|
||||
res.set("code", SPJ_AC);
|
||||
res.set("errMsg", err.split("ok ")[1]);
|
||||
} else if (err.startsWith("wrong answer ")) {
|
||||
res.set("code", SPJ_WA);
|
||||
res.set("errMsg", err.split("wrong answer ")[1]);
|
||||
} else if (err.startsWith("wrong output format ")) {
|
||||
res.set("code", SPJ_WA);
|
||||
res.set("errMsg", "May be output presentation error. " + err.split("wrong output format")[1]);
|
||||
} else if (err.startsWith("FAIL ")) {
|
||||
res.set("code", SPJ_ERROR);
|
||||
res.set("errMsg", err.split("FAIL ")[1]);
|
||||
} else {
|
||||
res.set("code", SPJ_ERROR);
|
||||
res.set("errMsg", err);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private JSONObject judgeOneCase(String userFileId,
|
||||
Integer testCaseId,
|
||||
String runDir,
|
||||
String testCasePath,
|
||||
Long testTime,
|
||||
Long maxTime,
|
||||
Long maxMemory,
|
||||
Integer maxStack,
|
||||
Long maxOutputSize,
|
||||
Boolean getUserOutput,
|
||||
Boolean isRemoveEOFBlank) throws SystemError {
|
||||
|
||||
// 调用安全沙箱使用测试点对程序进行测试
|
||||
JSONArray judgeResultList = SandboxRun.testCase(parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
testCasePath,
|
||||
testTime,
|
||||
maxOutputSize,
|
||||
maxStack,
|
||||
runConfig.getExeName(),
|
||||
userFileId);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
|
||||
|
||||
// 获取跑题用户输出或错误输出
|
||||
String userStdOut = ((JSONObject) judgeResult.get("files")).getStr("stdout");
|
||||
String userErrOut = ((JSONObject) judgeResult.get("files")).getStr("stderr");
|
||||
|
||||
StringBuffer errMsg = new StringBuffer();
|
||||
|
||||
// 获取程序运行内存 b-->kb
|
||||
long memory = judgeResult.getLong("memory") / 1024;
|
||||
// 获取程序运行时间 ns->ms
|
||||
long time = judgeResult.getLong("time") / 1000000;
|
||||
// 异常退出的状态码
|
||||
int exitCode = judgeResult.getInt("exitStatus");
|
||||
// 如果测试跑题无异常
|
||||
if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
|
||||
// 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
|
||||
if (time >= maxTime) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (memory >= maxMemory) {
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else {
|
||||
// 与原测试数据输出的md5进行对比 AC或者是WA
|
||||
result.set("status", compareOutput(testCaseId, userStdOut, isRemoveEOFBlank));
|
||||
}
|
||||
} else if (judgeResult.getInt("status").equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (exitCode != 0) {
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
if (exitCode < 32) {
|
||||
errMsg.append(String.format("ExitCode: %s (%s)\n", exitCode, SandboxRun.signals.get(exitCode)));
|
||||
} else {
|
||||
errMsg.append(String.format("ExitCode: %s\n", exitCode));
|
||||
}
|
||||
} else {
|
||||
result.set("status", judgeResult.getInt("status"));
|
||||
}
|
||||
|
||||
// b
|
||||
result.set("memory", memory);
|
||||
// ns->ms
|
||||
result.set("time", time);
|
||||
|
||||
if (!StringUtils.isEmpty(userStdOut)) {
|
||||
// 对于当前测试样例,用户程序的输出对应生成的文件
|
||||
FileWriter stdWriter = new FileWriter(runDir + "/" + testCaseId + ".out");
|
||||
stdWriter.write(userStdOut);
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(userErrOut)) {
|
||||
// 对于当前测试样例,用户的错误提示生成对应文件
|
||||
FileWriter errWriter = new FileWriter(runDir + "/" + testCaseId + ".err");
|
||||
errWriter.write(userErrOut);
|
||||
// 同时记录错误信息
|
||||
errMsg.append(userErrOut);
|
||||
}
|
||||
|
||||
// 记录该测试点的错误信息
|
||||
if (!StringUtils.isEmpty(errMsg.toString())) {
|
||||
result.set("errMsg", errMsg.toString());
|
||||
}
|
||||
|
||||
if (getUserOutput) { // 如果需要获取用户对于该题目的输出
|
||||
result.set("output", userStdOut);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static List<String> parseRunCommand(String command,
|
||||
Constants.RunConfig runConfig,
|
||||
String testCaseInputName,
|
||||
String userOutputName,
|
||||
String testCaseOutputName) {
|
||||
|
||||
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),
|
||||
runConfig.getExeName(), Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseInputName,
|
||||
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + userOutputName,
|
||||
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseOutputName);
|
||||
|
||||
return JudgeUtils.translateCommandline(command);
|
||||
}
|
||||
|
||||
|
||||
public JSONObject getTestCasesInfo(int testCaseId) {
|
||||
return (JSONObject) ((JSONArray) testCasesInfo.get("testCases")).get(testCaseId);
|
||||
}
|
||||
|
||||
// 根据评测结果与用户程序输出的字符串MD5进行对比
|
||||
private Integer compareOutput(int testCaseId, String userOutput, Boolean isRemoveEOFBlank) {
|
||||
|
||||
// 如果当前题目选择默认去掉字符串末位空格
|
||||
if (isRemoveEOFBlank) {
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());
|
||||
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("EOFStrippedOutputMd5"))) {
|
||||
return Constants.Judge.STATUS_ACCEPTED.getStatus();
|
||||
}
|
||||
} else { // 不选择默认去掉文末空格 与原数据进行对比
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());
|
||||
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("outputMd5"))) {
|
||||
return Constants.Judge.STATUS_ACCEPTED.getStatus();
|
||||
}
|
||||
}
|
||||
// 如果不AC,进行PE判断,否则为WA
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll("\\s+", "").getBytes());
|
||||
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("allStrippedOutputMd5"))) {
|
||||
return Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus();
|
||||
} else {
|
||||
return Constants.Judge.STATUS_WRONG_ANSWER.getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// 去除行末尾空白符
|
||||
public static String rtrim(String value) {
|
||||
if (value == null) return null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String[] strArr = value.split("\n");
|
||||
for (String str : strArr) {
|
||||
sb.append(str.replaceAll("\\s+$", "")).append("\n");
|
||||
}
|
||||
return sb.toString().replaceAll("\\s+$", "");
|
||||
}
|
||||
|
||||
/*
|
||||
交互题 暂时不启用
|
||||
*/
|
||||
private JSONObject interactOneCase(String userFileId,
|
||||
Integer testCaseId,
|
||||
String runDir,
|
||||
String testCaseInputFilePath,
|
||||
String testCaseOutputFilePath,
|
||||
Long maxTime,
|
||||
Long maxMemory,
|
||||
Integer maxStack,
|
||||
String userExeName,
|
||||
String spjExeName,
|
||||
Boolean getUserOutput) throws SystemError {
|
||||
|
||||
// 对于当前测试样例,用户程序的输出对应生成的文件(正常就输出数据,错误就是输出错误信息)
|
||||
String realUserOutputFile = runDir + File.separator + testCaseId + ".out";
|
||||
|
||||
// 特判程序的路径
|
||||
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + problemId + File.separator + spjExeName;
|
||||
String testCaseInputFileName = problemId + "_input";
|
||||
String testCaseOutputFileName = problemId + "_output";
|
||||
|
||||
JSONArray judgeResultList = SandboxRun.interactTestCase(
|
||||
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
userExeName,
|
||||
userFileId,
|
||||
testCaseInputFilePath,
|
||||
testCaseInputFileName,
|
||||
maxTime,
|
||||
maxStack,
|
||||
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, null, testCaseOutputFileName),
|
||||
spjRunConfig.getEnvs(),
|
||||
spjExeSrc,
|
||||
testCaseOutputFilePath,
|
||||
testCaseOutputFileName,
|
||||
spjExeName);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
// 用户程序输出写入文件
|
||||
FileWriter fileWriter = new FileWriter(realUserOutputFile);
|
||||
|
||||
JSONObject userJudgeResult = (JSONObject) judgeResultList.get(0);
|
||||
JSONObject spjJudgeResult = (JSONObject) judgeResultList.get(1);
|
||||
|
||||
// 特判程序输出或错误输出
|
||||
String spjStdOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stdout");
|
||||
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
|
||||
|
||||
// 获取用户程序运行内存 b-->kb
|
||||
long memory = userJudgeResult.getLong("memory") / 1024;
|
||||
// 获取用户程序运行时间 ns->ms
|
||||
long time = userJudgeResult.getLong("time") / 1000000;
|
||||
// 用户程序的退出状态码
|
||||
int userExitCode = userJudgeResult.getInt("exitStatus");
|
||||
// 记录错误信息
|
||||
StringBuffer errMsg = new StringBuffer();
|
||||
// 如果用户提交的代码运行无误
|
||||
if (userJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
// 如果运行超过题目限制时间,直接TLE
|
||||
if (time >= maxTime) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (memory >= maxMemory) { // 如果运行超过题目限制空间,直接MLE
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else { // 校验特判程序的输出
|
||||
// 根据特判程序的退出状态码进行判断
|
||||
if (spjJudgeResult.getInt("exitStatus") == SPJ_AC) {
|
||||
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
|
||||
} else if (spjJudgeResult.getInt("exitStatus") == SPJ_WA) {
|
||||
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
|
||||
} else {
|
||||
throw new SystemError(spjErrOut, spjStdOut, spjErrOut);
|
||||
}
|
||||
}
|
||||
} else if (userJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus()) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (userExitCode != 0) {
|
||||
if (userExitCode < 32) {
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
errMsg.append(String.format("ExitCode: %s (%s)\n", userExitCode, SandboxRun.signals.get(userExitCode)));
|
||||
} else {
|
||||
errMsg.append(String.format("ExitCode: %s\n", userExitCode));
|
||||
}
|
||||
String err = ((JSONObject) userJudgeResult.get("files")).getStr("stderr", null);
|
||||
errMsg.append(err);
|
||||
result.set("errMsg", errMsg.toString());
|
||||
fileWriter.write(err);
|
||||
} else {
|
||||
result.set("status", userJudgeResult.getInt("status"));
|
||||
}
|
||||
// kb
|
||||
result.set("memory", memory);
|
||||
// ms
|
||||
result.set("time", time);
|
||||
|
||||
if (getUserOutput) { // 如果需要获取用户对于该题目的输出,只提供特判程序输出
|
||||
result.set("output", spjStdOut);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package top.hcode.hoj.judge;
|
|||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -16,7 +17,9 @@ import top.hcode.hoj.pojo.entity.problem.Problem;
|
|||
import top.hcode.hoj.service.impl.JudgeCaseServiceImpl;
|
||||
import top.hcode.hoj.service.impl.JudgeServiceImpl;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
import top.hcode.hoj.util.JudgeUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -24,15 +27,17 @@ import java.util.*;
|
|||
@Component
|
||||
public class JudgeStrategy {
|
||||
|
||||
@Autowired
|
||||
@Resource
|
||||
private JudgeServiceImpl judgeService;
|
||||
|
||||
@Autowired
|
||||
@Resource
|
||||
private ProblemTestCaseUtils problemTestCaseUtils;
|
||||
|
||||
@Autowired
|
||||
@Resource
|
||||
private JudgeCaseServiceImpl judgeCaseService;
|
||||
|
||||
@Resource
|
||||
private JudgeRun judgeRun;
|
||||
|
||||
public HashMap<String, Object> judge(Problem problem, Judge judge) {
|
||||
|
||||
|
@ -41,19 +46,21 @@ public class JudgeStrategy {
|
|||
String userFileId = null;
|
||||
try {
|
||||
// 对用户源代码进行编译 获取tmpfs中的fileId
|
||||
userFileId = Compiler.compile(Constants.CompileConfig.getCompilerByLanguage(judge.getLanguage()), judge.getCode(), judge.getLanguage());
|
||||
userFileId = Compiler.compile(Constants.CompileConfig.getCompilerByLanguage(judge.getLanguage()),
|
||||
judge.getCode(), judge.getLanguage(), JudgeUtils.getProblemExtraFileMap(problem, "user"));
|
||||
// 测试数据文件所在文件夹
|
||||
String testCasesDir = Constants.JudgeDir.TEST_CASE_DIR.getContent() + File.separator + "problem_" + problem.getId();
|
||||
// 从文件中加载测试数据json
|
||||
JSONObject testCasesInfo = problemTestCaseUtils.loadTestCaseInfo(problem.getId(), testCasesDir, problem.getCaseVersion(), !StringUtils.isEmpty(problem.getSpjCode()));
|
||||
JSONObject testCasesInfo = problemTestCaseUtils.loadTestCaseInfo(problem.getId(), testCasesDir, problem.getCaseVersion(),
|
||||
problem.getJudgeMode());
|
||||
JSONArray testcaseList = (JSONArray) testCasesInfo.get("testCases");
|
||||
String version = testCasesInfo.getStr("version");
|
||||
// 检查是否为spj,同时是否有spj编译完成的文件,若不存在,就先编译生成该spj文件,同时也要检查版本
|
||||
Boolean hasSpjOrNotSpj = checkOrCompileSpj(problem, version);
|
||||
// 如果该题为spj,但是没有spj程序
|
||||
if (!hasSpjOrNotSpj) {
|
||||
|
||||
// 检查是否为spj或者interactive,同时是否有对应编译完成的文件,若不存在,就先编译生成该文件,同时也要检查版本
|
||||
Boolean isOk = checkOrCompileExtraProgram(problem, version);
|
||||
if (!isOk) {
|
||||
result.put("code", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
|
||||
result.put("errMsg", "The special judge code does not exist.");
|
||||
result.put("errMsg", "The special judge or interactive program code does not exist.");
|
||||
result.put("time", 0);
|
||||
result.put("memory", 0);
|
||||
return result;
|
||||
|
@ -62,28 +69,15 @@ public class JudgeStrategy {
|
|||
// 更新状态为评测数据中
|
||||
judge.setStatus(Constants.Judge.STATUS_JUDGING.getStatus());
|
||||
judgeService.saveOrUpdate(judge);
|
||||
// spj程序的名字
|
||||
String spjExeName = null;
|
||||
if (!StringUtils.isEmpty(problem.getSpjCode())) {
|
||||
spjExeName = Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage()).getExeName();
|
||||
}
|
||||
JudgeRun judgeRun = new JudgeRun(
|
||||
judge.getSubmitId(),
|
||||
problem.getId(),
|
||||
// 开始测试每个测试点
|
||||
List<JSONObject> allCaseResultList = judgeRun.judgeAllCase(judge.getSubmitId(),
|
||||
problem,
|
||||
judge.getLanguage(),
|
||||
testCasesDir,
|
||||
testCasesInfo,
|
||||
Constants.RunConfig.getRunnerByLanguage(judge.getLanguage()),
|
||||
Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage())
|
||||
);
|
||||
// 开始测试每个测试点
|
||||
List<JSONObject> allCaseResultList = judgeRun.judgeAllCase(
|
||||
userFileId,
|
||||
problem.getTimeLimit() * 1L,
|
||||
problem.getMemoryLimit() * 1024L,
|
||||
problem.getStackLimit(),
|
||||
false,
|
||||
problem.getIsRemoveEndBlank(),
|
||||
spjExeName);
|
||||
false);
|
||||
|
||||
// 对全部测试点结果进行评判,获取最终评判结果
|
||||
HashMap<String, Object> judgeInfo = getJudgeInfo(allCaseResultList, problem, judge);
|
||||
return judgeInfo;
|
||||
|
@ -121,18 +115,43 @@ public class JudgeStrategy {
|
|||
return result;
|
||||
}
|
||||
|
||||
private Boolean checkOrCompileExtraProgram(Problem problem, String version) throws CompileError, SystemError {
|
||||
|
||||
public Boolean checkOrCompileSpj(Problem problem, String version) throws CompileError, SystemError {
|
||||
// 如果是需要特判的题目,则需要检测特批程序是否已经编译,否则进行编译
|
||||
if (!StringUtils.isEmpty(problem.getSpjCode())) {
|
||||
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + problem.getSpjLanguage());
|
||||
// 如果不存在该已经编译好的特批程序,则需要再次进行编译 版本变动也需要重新编译
|
||||
if (!FileUtil.exist(Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator +
|
||||
problem.getId() + File.separator + spjCompiler.getExeName())
|
||||
|| !problem.getCaseVersion().equals(version)) {
|
||||
return Compiler.compileSpj(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage());
|
||||
}
|
||||
Constants.JudgeMode judgeMode = Constants.JudgeMode.getJudgeMode(problem.getJudgeMode());
|
||||
Constants.CompileConfig compiler;
|
||||
String filePath;
|
||||
|
||||
switch (judgeMode) {
|
||||
case DEFAULT:
|
||||
return true;
|
||||
case SPJ:
|
||||
compiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + problem.getSpjLanguage());
|
||||
filePath = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator +
|
||||
problem.getId() + File.separator + compiler.getExeName();
|
||||
|
||||
// 如果不存在该已经编译好的程序,则需要再次进行编译 版本变动也需要重新编译
|
||||
if (!FileUtil.exist(filePath) || !problem.getCaseVersion().equals(version)) {
|
||||
return Compiler.compileSpj(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage(),
|
||||
JudgeUtils.getProblemExtraFileMap(problem, "judge"));
|
||||
}
|
||||
|
||||
break;
|
||||
case INTERACTIVE:
|
||||
compiler = Constants.CompileConfig.getCompilerByLanguage("INTERACTIVE-" + problem.getSpjLanguage());
|
||||
filePath = Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent() + File.separator +
|
||||
problem.getId() + File.separator + compiler.getExeName();
|
||||
|
||||
// 如果不存在该已经编译好的程序,则需要再次进行编译 版本变动也需要重新编译
|
||||
if (!FileUtil.exist(filePath) || !problem.getCaseVersion().equals(version)) {
|
||||
return Compiler.compileInteractive(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage(),
|
||||
JudgeUtils.getProblemExtraFileMap(problem, "judge"));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("The problem mode is error:" + judgeMode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,14 +38,14 @@ public class ProblemTestCaseUtils {
|
|||
public JSONObject initTestCase(List<HashMap<String, Object>> testCases,
|
||||
Long problemId,
|
||||
String version,
|
||||
Boolean isSpj) throws SystemError, UnsupportedEncodingException {
|
||||
String mode) throws SystemError, UnsupportedEncodingException {
|
||||
|
||||
if (testCases == null || testCases.size() == 0) {
|
||||
throw new SystemError("题号为:" + problemId + "的评测数据为空!", null, "The test cases does not exist.");
|
||||
}
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("isSpj", isSpj);
|
||||
result.set("mode", mode);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", testCases.size());
|
||||
|
||||
|
@ -74,8 +74,8 @@ public class ProblemTestCaseUtils {
|
|||
FileWriter outFile = new FileWriter(testCasesDir + "/" + outputName, CharsetUtil.UTF_8);
|
||||
outFile.write(outputData);
|
||||
|
||||
// spj是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (!isSpj) {
|
||||
// spj或interactive是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
|
||||
// 原数据MD5
|
||||
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(outputData.getBytes()));
|
||||
// 原数据大小
|
||||
|
@ -98,13 +98,13 @@ public class ProblemTestCaseUtils {
|
|||
}
|
||||
|
||||
// 本地有文件,进行数据初始化 生成json文件
|
||||
public JSONObject initLocalTestCase(Boolean isSpj,
|
||||
public JSONObject initLocalTestCase(String mode,
|
||||
String version,
|
||||
String testCasesDir,
|
||||
List<ProblemCase> problemCaseList) {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("isSpj", isSpj);
|
||||
result.set("mode", mode);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", problemCaseList.size());
|
||||
result.set("testCases", new JSONArray());
|
||||
|
@ -120,8 +120,8 @@ public class ProblemTestCaseUtils {
|
|||
FileReader readFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(), CharsetUtil.UTF_8);
|
||||
String output = readFile.readString().replaceAll("\r\n", "\n");
|
||||
|
||||
// spj是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (!isSpj) {
|
||||
// spj或interactive是根据特判程序输出判断结果,所以无需初始化测试数据
|
||||
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
|
||||
// 原数据MD5
|
||||
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(output.getBytes()));
|
||||
// 原数据大小
|
||||
|
@ -144,23 +144,23 @@ public class ProblemTestCaseUtils {
|
|||
|
||||
|
||||
// 获取指定题目的info数据
|
||||
public JSONObject loadTestCaseInfo(Long problemId, String testCasesDir, String version, Boolean isSpj) throws SystemError, UnsupportedEncodingException {
|
||||
public JSONObject loadTestCaseInfo(Long problemId, String testCasesDir, String version, String mode) throws SystemError, UnsupportedEncodingException {
|
||||
if (FileUtil.exist(testCasesDir + File.separator + "info")) {
|
||||
FileReader fileReader = new FileReader(testCasesDir + File.separator + "info", CharsetUtil.UTF_8);
|
||||
String infoStr = fileReader.readString();
|
||||
JSONObject testcaseInfo = JSONUtil.parseObj(infoStr);
|
||||
// 测试样例被改动需要重新生成
|
||||
if (!testcaseInfo.getStr("version", null).equals(version)) {
|
||||
return tryInitTestCaseInfo(testCasesDir, problemId, version, isSpj);
|
||||
return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);
|
||||
}
|
||||
return testcaseInfo;
|
||||
} else {
|
||||
return tryInitTestCaseInfo(testCasesDir, problemId, version, isSpj);
|
||||
return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);
|
||||
}
|
||||
}
|
||||
|
||||
// 若没有测试数据,则尝试从数据库获取并且初始化到本地,如果数据库中该题目测试数据为空,rsync同步也出了问题,则直接判系统错误
|
||||
public JSONObject tryInitTestCaseInfo(String testCasesDir, Long problemId, String version, Boolean isSpj) throws SystemError, UnsupportedEncodingException {
|
||||
public JSONObject tryInitTestCaseInfo(String testCasesDir, Long problemId, String version, String mode) throws SystemError, UnsupportedEncodingException {
|
||||
|
||||
QueryWrapper<ProblemCase> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("pid", problemId);
|
||||
|
@ -180,7 +180,7 @@ public class ProblemTestCaseUtils {
|
|||
if (FileUtil.isEmpty(new File(testCasesDir))) { //如果本地对应文件夹也为空,说明文件丢失了
|
||||
throw new SystemError("problemID:[" + problemId + "] test case has not found.", null, null);
|
||||
} else {
|
||||
return initLocalTestCase(isSpj, version, testCasesDir, problemCases);
|
||||
return initLocalTestCase(mode, version, testCasesDir, problemCases);
|
||||
}
|
||||
} else {
|
||||
|
||||
|
@ -194,7 +194,7 @@ public class ProblemTestCaseUtils {
|
|||
testCases.add(tmp);
|
||||
}
|
||||
|
||||
return initTestCase(testCases, problemId, version, isSpj);
|
||||
return initTestCase(testCases, problemId, version, mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package top.hcode.hoj.judge;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONNull;
|
||||
import cn.hutool.json.JSONObject;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
@ -11,6 +10,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
|
@ -19,6 +19,7 @@ import top.hcode.hoj.util.Constants;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
|
@ -74,7 +75,7 @@ public class SandboxRun {
|
|||
|
||||
private static final int MEMORY_LIMIT_MB = 512;
|
||||
|
||||
private static final int STACK_LIMIT_MB = 128;
|
||||
private static final int STACK_LIMIT_MB = 256;
|
||||
|
||||
private SandboxRun() {
|
||||
|
||||
|
@ -200,9 +201,38 @@ public class SandboxRun {
|
|||
COMPILE_FILES.put(stderr);
|
||||
}
|
||||
|
||||
public static JSONArray compile(Long maxCpuTime, Long maxRealTime, Long maxMemory, Long maxStack, String srcName, String exeName,
|
||||
List<String> args, List<String> envs, String code, Boolean needCopyOutCached,
|
||||
Boolean needCopyOutExe, String copyOutDir) throws SystemError {
|
||||
/**
|
||||
* @param maxCpuTime 最大编译的cpu时间 ms
|
||||
* @param maxRealTime 最大编译的真实时间 ms
|
||||
* @param maxMemory 最大编译的空间 b
|
||||
* @param maxStack 最大编译的栈空间 b
|
||||
* @param srcName 编译的源文件名字
|
||||
* @param exeName 编译生成的exe文件名字
|
||||
* @param args 编译的cmd参数
|
||||
* @param envs 编译的环境变量
|
||||
* @param code 编译的源代码
|
||||
* @param extraFiles 编译所需的额外文件 key:文件名,value:文件内容
|
||||
* @param needCopyOutCached 是否需要生成编译后的用户程序exe文件
|
||||
* @param needCopyOutExe 是否需要生成用户程序的缓存文件,即生成用户程序id
|
||||
* @param copyOutDir 生成编译后的用户程序exe文件的指定路径
|
||||
* @MethodName compile
|
||||
* @Description 编译运行
|
||||
* @Return
|
||||
* @Since 2022/1/3
|
||||
*/
|
||||
public static JSONArray compile(Long maxCpuTime,
|
||||
Long maxRealTime,
|
||||
Long maxMemory,
|
||||
Long maxStack,
|
||||
String srcName,
|
||||
String exeName,
|
||||
List<String> args,
|
||||
List<String> envs,
|
||||
String code,
|
||||
HashMap<String, String> extraFiles,
|
||||
Boolean needCopyOutCached,
|
||||
Boolean needCopyOutExe,
|
||||
String copyOutDir) throws SystemError {
|
||||
JSONObject cmd = new JSONObject();
|
||||
cmd.set("args", args);
|
||||
cmd.set("env", envs);
|
||||
|
@ -215,10 +245,21 @@ public class SandboxRun {
|
|||
cmd.set("procLimit", maxProcessNumber);
|
||||
cmd.set("stackLimit", maxStack);
|
||||
|
||||
JSONObject file = new JSONObject();
|
||||
file.set("content", code);
|
||||
JSONObject fileContent = new JSONObject();
|
||||
fileContent.set("content", code);
|
||||
|
||||
JSONObject copyIn = new JSONObject();
|
||||
copyIn.set(srcName, file);
|
||||
copyIn.set(srcName, fileContent);
|
||||
|
||||
if (extraFiles != null) {
|
||||
for (Map.Entry<String, String> entry : extraFiles.entrySet()) {
|
||||
if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(entry.getValue())) {
|
||||
JSONObject content = new JSONObject();
|
||||
content.set("content", entry.getValue());
|
||||
copyIn.set(entry.getKey(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.set("copyIn", copyIn);
|
||||
cmd.set("copyOut", new JSONArray().put("stdout").put("stderr"));
|
||||
|
@ -241,8 +282,29 @@ public class SandboxRun {
|
|||
}
|
||||
|
||||
|
||||
public static JSONArray testCase(List<String> args, List<String> envs, String testCasePath, Long maxTime, Long maxOutputSize,
|
||||
Integer maxStack, String exeName, String fileId) throws SystemError {
|
||||
/**
|
||||
* @param args 普通评测运行cmd的命令参数
|
||||
* @param envs 普通评测运行的环境变量
|
||||
* @param testCasePath 题目数据的输入文件路径
|
||||
* @param maxTime 评测的最大限制时间 ms
|
||||
* @param maxOutputSize 评测的最大输出大小 kb
|
||||
* @param maxStack 评测的最大限制栈空间 mb
|
||||
* @param exeName 评测的用户程序名称
|
||||
* @param fileId 评测的用户程序文件id
|
||||
* @MethodName testCase
|
||||
* @Description 普通评测
|
||||
* @Return JSONArray
|
||||
* @Since 2022/1/3
|
||||
*/
|
||||
public static JSONArray testCase(List<String> args,
|
||||
List<String> envs,
|
||||
String testCasePath,
|
||||
Long maxTime,
|
||||
Long maxOutputSize,
|
||||
Integer maxStack,
|
||||
String exeName,
|
||||
String fileId) throws SystemError {
|
||||
|
||||
JSONObject cmd = new JSONObject();
|
||||
cmd.set("args", args);
|
||||
cmd.set("env", envs);
|
||||
|
@ -292,6 +354,22 @@ public class SandboxRun {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param args 特殊判题的运行cmd命令参数
|
||||
* @param envs 特殊判题的运行环境变量
|
||||
* @param userOutputFilePath 用户程序输出文件的路径
|
||||
* @param userOutputFileName 用户程序输出文件的名字
|
||||
* @param testCaseInputFilePath 题目数据的输入文件的路径
|
||||
* @param testCaseInputFileName 题目数据的输入文件的名字
|
||||
* @param testCaseOutputFilePath 题目数据的输出文件的路径
|
||||
* @param testCaseOutputFileName 题目数据的输出文件的路径
|
||||
* @param spjExeSrc 特殊判题的exe文件的路径
|
||||
* @param spjExeName 特殊判题的exe文件的名字
|
||||
* @MethodName spjCheckResult
|
||||
* @Description 特殊判题的评测
|
||||
* @Return JSONArray
|
||||
* @Since 2022/1/3
|
||||
*/
|
||||
public static JSONArray spjCheckResult(List<String> args,
|
||||
List<String> envs,
|
||||
String userOutputFilePath,
|
||||
|
@ -302,6 +380,7 @@ public class SandboxRun {
|
|||
String testCaseOutputFileName,
|
||||
String spjExeSrc,
|
||||
String spjExeName) throws SystemError {
|
||||
|
||||
JSONObject cmd = new JSONObject();
|
||||
cmd.set("args", args);
|
||||
cmd.set("env", envs);
|
||||
|
@ -367,23 +446,41 @@ public class SandboxRun {
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
交互跑题 暂时不启用
|
||||
|
||||
/**
|
||||
* @param args cmd的命令参数 评测运行的命令
|
||||
* @param envs 测评的环境变量
|
||||
* @param userExeName 用户程序的名字
|
||||
* @param userFileId 用户程序在编译后返回的id,主要是对应内存中已编译后的文件
|
||||
* @param userMaxTime 用户程序的最大测评时间 ms
|
||||
* @param userMaxStack 用户程序的最大测评栈空间 mb
|
||||
* @param testCaseInputPath 题目数据的输入文件路径
|
||||
* @param testCaseInputFileName 题目数据的输入文件名字
|
||||
* @param testCaseOutputFilePath 题目数据的输出文件路径
|
||||
* @param testCaseOutputFileName 题目数据的输出文件名字
|
||||
* @param interactArgs 交互程序运行的cmd命令参数
|
||||
* @param interactEnvs 交互程序运行的环境变量
|
||||
* @param interactExeSrc 交互程序的exe文件路径
|
||||
* @param interactExeName 交互程序的exe文件名字
|
||||
* @MethodName interactTestCase
|
||||
* @Description 交互评测
|
||||
* @Return JSONArray
|
||||
* @Since 2022/1/3
|
||||
*/
|
||||
public static JSONArray interactTestCase(List<String> args,
|
||||
List<String> envs,
|
||||
String userExeName,
|
||||
String userFileId,
|
||||
Long userMaxTime,
|
||||
Integer userMaxStack,
|
||||
String testCaseInputPath,
|
||||
String testCaseInputFileName,
|
||||
Long maxTime,
|
||||
Integer maxStack,
|
||||
List<String> spjArgs,
|
||||
List<String> spjEnvs,
|
||||
String spjExeSrc,
|
||||
String testCaseOutputFilePath,
|
||||
String testCaseOutputFileName,
|
||||
String spjExeName) throws SystemError {
|
||||
List<String> interactArgs,
|
||||
List<String> interactEnvs,
|
||||
String interactExeSrc,
|
||||
String interactExeName) throws SystemError {
|
||||
|
||||
/**
|
||||
* 注意:用户源代码需要先编译,若是通过编译需要先将文件存入内存,再利用管道判题,同时特殊判题程序必须已编译且存在(否则判题失败,系统错误)!
|
||||
|
@ -408,12 +505,12 @@ public class SandboxRun {
|
|||
pipeInputCmd.set("files", JSONUtil.parseArray(inTmp, false));
|
||||
|
||||
// ms-->ns
|
||||
pipeInputCmd.set("cpuLimit", maxTime * 1000 * 1000L);
|
||||
pipeInputCmd.set("realCpuLimit", maxTime * 1000 * 1000L * 3);
|
||||
pipeInputCmd.set("cpuLimit", userMaxTime * 1000 * 1000L);
|
||||
pipeInputCmd.set("realCpuLimit", userMaxTime * 1000 * 1000L * 3);
|
||||
// byte
|
||||
pipeInputCmd.set("memoryLimit", MEMORY_LIMIT_MB * 1024 * 1024L);
|
||||
pipeInputCmd.set("clockLimit", maxProcessNumber);
|
||||
pipeInputCmd.set("stackLimit", maxStack * 1024 * 1024L);
|
||||
pipeInputCmd.set("stackLimit", userMaxStack * 1024 * 1024L);
|
||||
|
||||
JSONObject exeFile = new JSONObject();
|
||||
exeFile.set("fileId", userFileId);
|
||||
|
@ -426,8 +523,8 @@ public class SandboxRun {
|
|||
|
||||
// 管道输出,用户程序输出数据经过特殊判题程序后,得到的最终输出结果。
|
||||
JSONObject pipeOutputCmd = new JSONObject();
|
||||
pipeOutputCmd.set("args", spjArgs);
|
||||
pipeOutputCmd.set("env", spjEnvs);
|
||||
pipeOutputCmd.set("args", interactArgs);
|
||||
pipeOutputCmd.set("env", interactEnvs);
|
||||
|
||||
JSONArray outFiles = new JSONArray();
|
||||
|
||||
|
@ -453,7 +550,7 @@ public class SandboxRun {
|
|||
pipeOutputCmd.set("stackLimit", STACK_LIMIT_MB * 1024 * 1024L);
|
||||
|
||||
JSONObject spjExeFile = new JSONObject();
|
||||
spjExeFile.set("src", spjExeSrc);
|
||||
spjExeFile.set("src", interactExeSrc);
|
||||
|
||||
JSONObject stdInputFileSrc = new JSONObject();
|
||||
stdInputFileSrc.set("src", testCaseInputPath);
|
||||
|
@ -462,7 +559,7 @@ public class SandboxRun {
|
|||
stdOutFileSrc.set("src", testCaseOutputFilePath);
|
||||
|
||||
JSONObject spjCopyIn = new JSONObject();
|
||||
spjCopyIn.set(spjExeName, spjExeFile);
|
||||
spjCopyIn.set(interactExeName, spjExeFile);
|
||||
spjCopyIn.set(testCaseInputFileName, stdInputFileSrc);
|
||||
spjCopyIn.set(testCaseOutputFileName, stdOutFileSrc);
|
||||
|
||||
|
@ -502,8 +599,10 @@ public class SandboxRun {
|
|||
|
||||
// 调用判题安全沙箱
|
||||
JSONArray result = instance.run("/run", param);
|
||||
JSONObject tmp = (JSONObject) result.get(0);
|
||||
((JSONObject) result.get(0)).set("status", RESULT_MAP_STATUS.get(tmp.getStr("status")));
|
||||
JSONObject userRes = (JSONObject) result.get(0);
|
||||
JSONObject interactiveRes = (JSONObject) result.get(1);
|
||||
userRes.set("status", RESULT_MAP_STATUS.get(userRes.getStr("status")));
|
||||
interactiveRes.set("status", RESULT_MAP_STATUS.get(interactiveRes.getStr("status")));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -598,7 +697,7 @@ public class SandboxRun {
|
|||
}
|
||||
}]
|
||||
|
||||
3. SPJ
|
||||
3. Interactive
|
||||
|
||||
{
|
||||
"pipeMapping": [
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package top.hcode.hoj.judge.entity;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/2 20:58
|
||||
* @Description: 评测题目的传输类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
public class JudgeDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 666L;
|
||||
|
||||
/**
|
||||
* 当前题目评测点的的编号
|
||||
*/
|
||||
private Integer testCaseId;
|
||||
|
||||
/**
|
||||
* 当前题目评测点的输入文件的绝对路径
|
||||
*/
|
||||
private String testCaseInputPath;
|
||||
|
||||
/**
|
||||
* 当前题目评测点的输出文件的绝对路径
|
||||
*/
|
||||
private String testCaseOutputPath;
|
||||
|
||||
/**
|
||||
* 当前题目评测点的输出字符大小限制 kb
|
||||
*/
|
||||
private Long maxOutputSize;
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package top.hcode.hoj.judge.entity;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/3 11:53
|
||||
* @Description: 一次评测全局通用的传输实体类
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
public class JudgeGlobalDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 888L;
|
||||
|
||||
/**
|
||||
* 当前评测题目的id
|
||||
*/
|
||||
private Long problemId;
|
||||
|
||||
/**
|
||||
* 当前评测题目的模式
|
||||
*/
|
||||
private Constants.JudgeMode judgeMode;
|
||||
|
||||
/**
|
||||
* 用户程序在沙盒编译后对应内存文件的id,运行时需要传入
|
||||
*/
|
||||
private String userFileId;
|
||||
|
||||
/**
|
||||
* 整个评测的工作目录
|
||||
*/
|
||||
private String runDir;
|
||||
|
||||
/**
|
||||
* 判题沙盒评测程序的最大实际时间,一般为题目最大限制时间+200ms
|
||||
*/
|
||||
private Long testTime;
|
||||
|
||||
/**
|
||||
* 当前题目评测的最大时间限制 ms
|
||||
*/
|
||||
private Long maxTime;
|
||||
|
||||
/**
|
||||
* 当前题目评测的最大空间限制 kb
|
||||
*/
|
||||
private Long maxMemory;
|
||||
|
||||
/**
|
||||
* 当前题目评测的最大栈空间限制 mb
|
||||
*/
|
||||
private Integer maxStack;
|
||||
|
||||
/**
|
||||
* 评测数据json内容
|
||||
*/
|
||||
private JSONObject testCaseInfo;
|
||||
|
||||
/**
|
||||
* 交互程序或特判程序所需的额外文件 key:文件名,value:文件路径
|
||||
*/
|
||||
private HashMap<String,String> judgeExtraFiles;
|
||||
|
||||
/**
|
||||
* 普通评测的命令配置
|
||||
*/
|
||||
Constants.RunConfig runConfig;
|
||||
|
||||
/**
|
||||
* 特殊判题的命令配置
|
||||
*/
|
||||
Constants.RunConfig spjRunConfig;
|
||||
|
||||
/**
|
||||
* 交互判题的命令配置
|
||||
*/
|
||||
Constants.RunConfig interactiveRunConfig;
|
||||
|
||||
/**
|
||||
* 是否需要生成用户程序输出的文件
|
||||
*/
|
||||
private Boolean needUserOutputFile;
|
||||
|
||||
/**
|
||||
* 是否需要自动移除评测数据的行末空格
|
||||
*/
|
||||
private Boolean removeEOLBlank;
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package top.hcode.hoj.judge.entity;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/3 15:27
|
||||
* @Description: 单个测评结果实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
public class SandBoxRes {
|
||||
/**
|
||||
* 单个程序的状态码
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 单个程序的退出码
|
||||
*/
|
||||
private Integer exitCode;
|
||||
|
||||
/**
|
||||
* 单个程序的运行所耗空间 kb
|
||||
*/
|
||||
private Long memory;
|
||||
|
||||
/**
|
||||
* 单个程序的运行所耗时间 ms
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
/**
|
||||
* 单个程序的标准输出
|
||||
*/
|
||||
private String stdout;
|
||||
|
||||
/**
|
||||
* 单个程序的错误信息
|
||||
*/
|
||||
private String stderr;
|
||||
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package top.hcode.hoj.judge.task;
|
||||
|
||||
import cn.hutool.core.io.file.FileWriter;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.AbstractJudge;
|
||||
import top.hcode.hoj.judge.SandboxRun;
|
||||
import top.hcode.hoj.judge.entity.JudgeDTO;
|
||||
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
|
||||
import top.hcode.hoj.judge.entity.SandBoxRes;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/2 21:18
|
||||
* @Description: 普通评测
|
||||
*/
|
||||
@Component
|
||||
public class DefaultJudge extends AbstractJudge {
|
||||
@Override
|
||||
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
|
||||
// 调用安全沙箱使用测试点对程序进行测试
|
||||
return SandboxRun.testCase(
|
||||
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
judgeDTO.getTestCaseInputPath(),
|
||||
judgeGlobalDTO.getTestTime(),
|
||||
judgeDTO.getMaxOutputSize(),
|
||||
judgeGlobalDTO.getMaxStack(),
|
||||
runConfig.getExeName(),
|
||||
judgeGlobalDTO.getUserFileId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
StringBuilder errMsg = new StringBuilder();
|
||||
// 如果测试跑题无异常
|
||||
if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
|
||||
|
||||
// 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
|
||||
if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else {
|
||||
// 与原测试数据输出的md5进行对比 AC或者是WA
|
||||
JSONObject testcaseInfo = (JSONObject) ((JSONArray) judgeGlobalDTO.getTestCaseInfo().get("testCases")).get(judgeDTO.getTestCaseId() - 1);
|
||||
result.set("status", compareOutput(sandBoxRes.getStdout(), judgeGlobalDTO.getRemoveEOLBlank(), testcaseInfo));
|
||||
}
|
||||
} else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (sandBoxRes.getExitCode() != 0) {
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
if (sandBoxRes.getExitCode() < 32) {
|
||||
errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
|
||||
} else {
|
||||
errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
|
||||
}
|
||||
} else {
|
||||
result.set("status", sandBoxRes.getStatus());
|
||||
}
|
||||
|
||||
// b
|
||||
result.set("memory", sandBoxRes.getMemory());
|
||||
// ns->ms
|
||||
result.set("time", sandBoxRes.getTime());
|
||||
|
||||
if (!StringUtils.isEmpty(sandBoxRes.getStdout())) {
|
||||
// 对于当前测试样例,用户程序的输出对应生成的文件
|
||||
FileWriter stdWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".out");
|
||||
stdWriter.write(sandBoxRes.getStdout());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
|
||||
// 对于当前测试样例,用户的错误提示生成对应文件
|
||||
FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".err");
|
||||
errWriter.write(sandBoxRes.getStderr());
|
||||
// 同时记录错误信息
|
||||
errMsg.append(sandBoxRes.getStderr());
|
||||
}
|
||||
|
||||
// 记录该测试点的错误信息
|
||||
if (!StringUtils.isEmpty(errMsg.toString())) {
|
||||
result.set("errMsg", errMsg.toString());
|
||||
}
|
||||
|
||||
if (judgeGlobalDTO.getNeedUserOutputFile()) { // 如果需要获取用户对于该题目的输出
|
||||
result.set("output", sandBoxRes.getStdout());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据评测结果与用户程序输出的字符串MD5进行对比
|
||||
private Integer compareOutput(String userOutput, Boolean isRemoveEOLBlank, JSONObject testcaseInfo) {
|
||||
|
||||
// 如果当前题目选择默认去掉字符串末位空格
|
||||
if (isRemoveEOLBlank) {
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());
|
||||
if (userOutputMd5.equals(testcaseInfo.getStr("EOFStrippedOutputMd5"))) {
|
||||
return Constants.Judge.STATUS_ACCEPTED.getStatus();
|
||||
}
|
||||
} else { // 不选择默认去掉文末空格 与原数据进行对比
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());
|
||||
if (userOutputMd5.equals(testcaseInfo.getStr("outputMd5"))) {
|
||||
return Constants.Judge.STATUS_ACCEPTED.getStatus();
|
||||
}
|
||||
}
|
||||
// 如果不AC,进行PE判断,否则为WA
|
||||
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll("\\s+", "").getBytes());
|
||||
if (userOutputMd5.equals(testcaseInfo.getStr("allStrippedOutputMd5"))) {
|
||||
return Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus();
|
||||
} else {
|
||||
return Constants.Judge.STATUS_WRONG_ANSWER.getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 去除行末尾空白符
|
||||
private String rtrim(String value) {
|
||||
if (value == null) return null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String[] strArr = value.split("\n");
|
||||
for (String str : strArr) {
|
||||
sb.append(str.replaceAll("\\s+$", "")).append("\n");
|
||||
}
|
||||
return sb.toString().replaceAll("\\s+$", "");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package top.hcode.hoj.judge.task;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.AbstractJudge;
|
||||
import top.hcode.hoj.judge.SandboxRun;
|
||||
import top.hcode.hoj.judge.entity.JudgeDTO;
|
||||
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
|
||||
import top.hcode.hoj.judge.entity.SandBoxRes;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/2 23:24
|
||||
* @Description: 交互评测
|
||||
*/
|
||||
@Component
|
||||
public class InteractiveJudge extends AbstractJudge {
|
||||
|
||||
@Override
|
||||
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
|
||||
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
|
||||
Constants.RunConfig interactiveRunConfig = judgeGlobalDTO.getInteractiveRunConfig();
|
||||
|
||||
// 交互程序的路径
|
||||
String interactiveExeSrc = Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent()
|
||||
+ File.separator + judgeGlobalDTO.getProblemId() + File.separator + interactiveRunConfig.getExeName();
|
||||
|
||||
String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
|
||||
String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
|
||||
|
||||
// 其实交互题不存在用户输出文件的,为了统一格式造了个名字
|
||||
String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
|
||||
|
||||
JSONArray jsonArray = SandboxRun.interactTestCase(
|
||||
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
runConfig.getExeName(),
|
||||
judgeGlobalDTO.getUserFileId(),
|
||||
judgeGlobalDTO.getTestTime(),
|
||||
judgeGlobalDTO.getMaxStack(),
|
||||
judgeDTO.getTestCaseInputPath(),
|
||||
testCaseInputFileName,
|
||||
judgeDTO.getTestCaseOutputPath(),
|
||||
testCaseOutputFileName,
|
||||
parseRunCommand(interactiveRunConfig.getCommand(), interactiveRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
|
||||
interactiveRunConfig.getEnvs(),
|
||||
interactiveExeSrc,
|
||||
interactiveRunConfig.getExeName());
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
// 记录错误信息
|
||||
StringBuilder errMsg = new StringBuilder();
|
||||
|
||||
int userExitCode = userSandBoxRes.getExitCode();
|
||||
|
||||
if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
|
||||
// 如果运行超过题目限制时间,直接TLE
|
||||
if (userSandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (userSandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) { // 如果运行超过题目限制空间,直接MLE
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else {
|
||||
// 根据交互程序的退出状态码及输出进行判断
|
||||
JSONObject interactiveCheckRes = checkInteractiveRes(interactiveSandBoxRes);
|
||||
int code = interactiveCheckRes.getInt("code");
|
||||
if (code == SPJ_WA) {
|
||||
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
|
||||
} else if (code == SPJ_AC) {
|
||||
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
|
||||
} else if (code == SPJ_PE) {
|
||||
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
|
||||
} else {
|
||||
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
|
||||
}
|
||||
|
||||
String spjErrMsg = interactiveCheckRes.getStr("errMsg");
|
||||
if (!StringUtils.isEmpty(spjErrMsg)) {
|
||||
errMsg.append(spjErrMsg).append(" ");
|
||||
}
|
||||
}
|
||||
} else if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if ((userExitCode != 0 && userExitCode != 13) || (userExitCode == 13 && interactiveSandBoxRes.getExitCode() == 0)) {
|
||||
// Broken Pipe
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
if (userExitCode < 32) {
|
||||
errMsg.append(String.format("Your program return exitCode: %s (%s)\n", userExitCode, SandboxRun.signals.get(userExitCode)));
|
||||
} else {
|
||||
errMsg.append(String.format("Your program return exitCode: %s\n", userExitCode));
|
||||
}
|
||||
} else {
|
||||
result.set("status", interactiveSandBoxRes.getStatus());
|
||||
errMsg.append(interactiveSandBoxRes.getStderr()).append(" ");
|
||||
if (interactiveSandBoxRes.getExitCode() !=0 && !StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
|
||||
errMsg.append(String.format("Interactive program exited with code: %s",interactiveSandBoxRes.getExitCode()));
|
||||
}
|
||||
}
|
||||
// kb
|
||||
result.set("memory", userSandBoxRes.getMemory());
|
||||
// ms
|
||||
result.set("time", userSandBoxRes.getTime());
|
||||
|
||||
// 记录该测试点的错误信息
|
||||
if (!StringUtils.isEmpty(errMsg.toString())) {
|
||||
result.set("errMsg", errMsg.toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private JSONObject checkInteractiveRes(SandBoxRes interactiveSandBoxRes) {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
int exitCode = interactiveSandBoxRes.getExitCode();
|
||||
|
||||
// 获取跑题用户输出或错误输出
|
||||
if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
|
||||
result.set("errMsg", interactiveSandBoxRes.getStderr());
|
||||
}
|
||||
|
||||
// 如果程序无异常
|
||||
if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
|
||||
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
result.set("code", SPJ_AC);
|
||||
} else {
|
||||
result.set("code", exitCode);
|
||||
}
|
||||
} else if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_RUNTIME_ERROR.getStatus())) {
|
||||
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
|
||||
result.set("code", exitCode);
|
||||
} else {
|
||||
if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
|
||||
// 适配testlib.h 根据错误信息前缀判断
|
||||
return parseTestLibErr(interactiveSandBoxRes.getStderr());
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package top.hcode.hoj.judge.task;
|
||||
|
||||
import cn.hutool.core.io.file.FileWriter;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.common.exception.SystemError;
|
||||
import top.hcode.hoj.judge.AbstractJudge;
|
||||
import top.hcode.hoj.judge.SandboxRun;
|
||||
import top.hcode.hoj.judge.entity.JudgeDTO;
|
||||
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
|
||||
import top.hcode.hoj.judge.entity.SandBoxRes;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2022/1/2 22:23
|
||||
* @Description: 特殊判题 支持testlib
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class SpecialJudge extends AbstractJudge {
|
||||
@Override
|
||||
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
|
||||
// 调用安全沙箱使用测试点对程序进行测试
|
||||
return SandboxRun.testCase(
|
||||
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
|
||||
runConfig.getEnvs(),
|
||||
judgeDTO.getTestCaseInputPath(),
|
||||
judgeGlobalDTO.getTestTime(),
|
||||
judgeDTO.getMaxOutputSize(),
|
||||
judgeGlobalDTO.getMaxStack(),
|
||||
runConfig.getExeName(),
|
||||
judgeGlobalDTO.getUserFileId());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
StringBuilder errMsg = new StringBuilder();
|
||||
// 如果测试跑题无异常
|
||||
if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
|
||||
|
||||
// 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
|
||||
if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
|
||||
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
|
||||
} else {
|
||||
|
||||
// 对于当前测试样例,用户程序的输出对应生成的文件
|
||||
String userOutputFilePath = judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".out";
|
||||
FileWriter stdWriter = new FileWriter(userOutputFilePath);
|
||||
stdWriter.write(sandBoxRes.getStdout());
|
||||
|
||||
Constants.RunConfig spjRunConfig = judgeGlobalDTO.getSpjRunConfig();
|
||||
|
||||
// 特判程序的路径
|
||||
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator
|
||||
+ judgeGlobalDTO.getProblemId() + File.separator + spjRunConfig.getExeName();
|
||||
|
||||
String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
|
||||
String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
|
||||
String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
|
||||
// 进行spj程序运行比对
|
||||
JSONObject spjResult = spjRunAndCheckResult(userOutputFilePath,
|
||||
userOutputFileName,
|
||||
judgeDTO.getTestCaseInputPath(),
|
||||
testCaseInputFileName,
|
||||
judgeDTO.getTestCaseOutputPath(),
|
||||
testCaseOutputFileName,
|
||||
spjExeSrc,
|
||||
spjRunConfig);
|
||||
int code = spjResult.getInt("code");
|
||||
if (code == SPJ_WA) {
|
||||
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
|
||||
} else if (code == SPJ_AC) {
|
||||
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
|
||||
} else if (code == SPJ_PE) {
|
||||
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
|
||||
} else {
|
||||
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
|
||||
}
|
||||
|
||||
String spjErrMsg = spjResult.getStr("errMsg");
|
||||
if (!StringUtils.isEmpty(spjErrMsg)) {
|
||||
errMsg.append(spjErrMsg).append(" ");
|
||||
}
|
||||
|
||||
}
|
||||
} else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
|
||||
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
|
||||
} else if (sandBoxRes.getExitCode() != 0) {
|
||||
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
|
||||
if (sandBoxRes.getExitCode() < 32) {
|
||||
errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
|
||||
} else {
|
||||
errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
|
||||
}
|
||||
} else {
|
||||
result.set("status", sandBoxRes.getStatus());
|
||||
}
|
||||
|
||||
// b
|
||||
result.set("memory", sandBoxRes.getMemory());
|
||||
// ns->ms
|
||||
result.set("time", sandBoxRes.getTime());
|
||||
|
||||
// 记录该测试点的错误信息
|
||||
if (!StringUtils.isEmpty(errMsg.toString())) {
|
||||
result.set("errMsg", errMsg.toString());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
|
||||
// 同时记录错误信息
|
||||
errMsg.append(sandBoxRes.getStderr());
|
||||
// 对于当前测试样例,用户的错误提示生成对应文件
|
||||
FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".err");
|
||||
errWriter.write(sandBoxRes.getStderr());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private JSONObject spjRunAndCheckResult(String userOutputFilePath,
|
||||
String userOutputFileName,
|
||||
String testCaseInputFilePath,
|
||||
String testCaseInputFileName,
|
||||
String testCaseOutputFilePath,
|
||||
String testCaseOutputFileName,
|
||||
String spjExeSrc,
|
||||
Constants.RunConfig spjRunConfig) throws SystemError {
|
||||
|
||||
// 调用安全沙箱运行spj程序
|
||||
JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(
|
||||
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
|
||||
spjRunConfig.getEnvs(),
|
||||
userOutputFilePath,
|
||||
userOutputFileName,
|
||||
testCaseInputFilePath,
|
||||
testCaseInputFileName,
|
||||
testCaseOutputFilePath,
|
||||
testCaseOutputFileName,
|
||||
spjExeSrc,
|
||||
spjRunConfig.getExeName());
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);
|
||||
|
||||
// 获取跑题用户输出或错误输出
|
||||
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
|
||||
|
||||
if (!StringUtils.isEmpty(spjErrOut)) {
|
||||
result.set("errMsg", spjErrOut);
|
||||
}
|
||||
|
||||
// 退出状态码
|
||||
int exitCode = spjJudgeResult.getInt("exitStatus");
|
||||
// 如果测试跑题无异常
|
||||
if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
|
||||
result.set("code", SPJ_AC);
|
||||
} else {
|
||||
result.set("code", exitCode);
|
||||
}
|
||||
} else if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_RUNTIME_ERROR.getStatus()) {
|
||||
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
|
||||
result.set("code", exitCode);
|
||||
} else {
|
||||
if (!StringUtils.isEmpty(spjErrOut)) {
|
||||
// 适配testlib.h 根据错误信息前缀判断
|
||||
return parseTestLibErr(spjErrOut);
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.set("code", SPJ_ERROR);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import top.hcode.hoj.common.exception.SystemError;
|
|||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -19,7 +21,9 @@ public interface JudgeService extends IService<Judge> {
|
|||
|
||||
Judge Judge(Problem problem, Judge judge);
|
||||
|
||||
Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError;
|
||||
Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String,String> extraFiles) throws SystemError;
|
||||
|
||||
Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap<String,String> extraFiles) throws SystemError;
|
||||
|
||||
void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score,Integer useTime);
|
||||
}
|
||||
|
|
|
@ -78,10 +78,17 @@ public class JudgeServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements
|
|||
return judge;
|
||||
}
|
||||
|
||||
public Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError {
|
||||
return Compiler.compileSpj(code, pid, spjLanguage);
|
||||
@Override
|
||||
public Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String, String> extraFiles) throws SystemError {
|
||||
return Compiler.compileSpj(code, pid, spjLanguage, extraFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap<String, String> extraFiles) throws SystemError {
|
||||
return Compiler.compileInteractive(code, pid, interactiveLanguage, extraFiles);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score, Integer useTime) {
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ public class Constants {
|
|||
|
||||
CF_REMOTE_JUDGE_ACCOUNT("Codeforces Remote Judge Account");
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
RemoteJudge(String remoteJudgeName) {
|
||||
this.name = remoteJudgeName;
|
||||
|
@ -108,6 +108,30 @@ public class Constants {
|
|||
}
|
||||
|
||||
|
||||
public enum JudgeMode {
|
||||
DEFAULT("default"),
|
||||
SPJ("spj"),
|
||||
INTERACTIVE("interactive");
|
||||
|
||||
private final String mode;
|
||||
|
||||
JudgeMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static JudgeMode getJudgeMode(String mode){
|
||||
for (JudgeMode judgeMode : JudgeMode.values()) {
|
||||
if (judgeMode.getMode().equals(mode)) {
|
||||
return judgeMode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum JudgeDir {
|
||||
|
||||
|
@ -117,10 +141,12 @@ public class Constants {
|
|||
|
||||
SPJ_WORKPLACE_DIR("/judge/spj"),
|
||||
|
||||
INTERACTIVE_WORKPLACE_DIR("/judge/interactive"),
|
||||
|
||||
TMPFS_DIR("/w");
|
||||
|
||||
|
||||
private String content;
|
||||
private final String content;
|
||||
|
||||
JudgeDir(String content) {
|
||||
this.content = content;
|
||||
|
@ -134,7 +160,7 @@ public class Constants {
|
|||
public static List<String> defaultEnv = Arrays.asList(
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LANG=en_US.UTF-8",
|
||||
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8","HOME=/w");
|
||||
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "HOME=/w");
|
||||
|
||||
public static List<String> python3Env = Arrays.asList("LANG=en_US.UTF-8",
|
||||
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "PYTHONIOENCODING=utf-8");
|
||||
|
@ -143,7 +169,6 @@ public class Constants {
|
|||
"GOCACHE=off", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8");
|
||||
|
||||
public static List<String> javaEnv = Arrays.asList("HOME=/w");
|
||||
/*
|
||||
{0} --> tmpfs_dir
|
||||
{1} --> srcName
|
||||
|
@ -158,7 +183,7 @@ public class Constants {
|
|||
|
||||
CPPWithO2("C++ With O2", "main.cpp", "main", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
|
||||
|
||||
JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", javaEnv),
|
||||
JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", defaultEnv),
|
||||
|
||||
PYTHON2("Python2", "main.py", "main.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python -m py_compile ./{1}", defaultEnv),
|
||||
|
||||
|
@ -166,20 +191,24 @@ public class Constants {
|
|||
|
||||
GOLANG("Golang", "main.go", "main", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/go build -o {2} {1}", defaultEnv),
|
||||
|
||||
CS("C#","Main.cs","main",5000L,10000L,512 * 1024 * 1024L,"/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}",defaultEnv),
|
||||
CS("C#", "Main.cs", "main", 5000L, 10000L, 512 * 1024 * 1024L, "/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}", defaultEnv),
|
||||
|
||||
SPJ_C("SPJ-C", "spj.c", "spj", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
|
||||
|
||||
SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
|
||||
SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
|
||||
|
||||
private String language;
|
||||
private String srcName;
|
||||
private String exeName;
|
||||
private Long maxCpuTime;
|
||||
private Long maxRealTime;
|
||||
private Long maxMemory;
|
||||
private String command;
|
||||
private List<String> envs;
|
||||
INTERACTIVE_C("INTERACTIVE-C", "interactive.c", "interactive", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
|
||||
|
||||
INTERACTIVE_CPP("INTERACTIVE-C++", "interactive.cpp", "interactive", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
|
||||
|
||||
private final String language;
|
||||
private final String srcName;
|
||||
private final String exeName;
|
||||
private final Long maxCpuTime;
|
||||
private final Long maxRealTime;
|
||||
private final Long maxMemory;
|
||||
private final String command;
|
||||
private final List<String> envs;
|
||||
|
||||
CompileConfig(String language, String srcName, String exeName, Long maxCpuTime, Long maxRealTime, Long maxMemory,
|
||||
String command, List<String> envs) {
|
||||
|
@ -264,12 +293,16 @@ public class Constants {
|
|||
|
||||
SPJ_C("SPJ-C", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
|
||||
|
||||
SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv);
|
||||
SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
|
||||
|
||||
private String language;
|
||||
private String command;
|
||||
private String exeName;
|
||||
private List<String> envs;
|
||||
INTERACTIVE_C("INTERACTIVE-C", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv),
|
||||
|
||||
INTERACTIVE_CPP("INTERACTIVE-C++", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv);
|
||||
|
||||
private final String language;
|
||||
private final String command;
|
||||
private final String exeName;
|
||||
private final List<String> envs;
|
||||
|
||||
RunConfig(String language, String command, String exeName, List<String> envs) {
|
||||
this.language = language;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package top.hcode.hoj.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
|
@ -11,7 +14,21 @@ import java.util.StringTokenizer;
|
|||
*/
|
||||
public class JudgeUtils {
|
||||
|
||||
public static List<String> translateCommandline(String toProcess){
|
||||
@SuppressWarnings("All")
|
||||
public static HashMap<String, String> getProblemExtraFileMap(Problem problem, String type) {
|
||||
if ("user".equals(type)) {
|
||||
if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
|
||||
return (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
|
||||
}
|
||||
} else if ("judge".equals(type)) {
|
||||
if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
|
||||
return (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<String> translateCommandline(String toProcess) {
|
||||
if (toProcess != null && !toProcess.isEmpty()) {
|
||||
int state = 0;
|
||||
StringTokenizer tok = new StringTokenizer(toProcess, "\"' ", true);
|
||||
|
@ -19,10 +36,10 @@ public class JudgeUtils {
|
|||
StringBuilder current = new StringBuilder();
|
||||
boolean lastTokenHasBeenQuoted = false;
|
||||
|
||||
while(true) {
|
||||
while(tok.hasMoreTokens()) {
|
||||
while (true) {
|
||||
while (tok.hasMoreTokens()) {
|
||||
String nextTok = tok.nextToken();
|
||||
switch(state) {
|
||||
switch (state) {
|
||||
case 1:
|
||||
if ("'".equals(nextTok)) {
|
||||
lastTokenHasBeenQuoted = true;
|
||||
|
@ -71,4 +88,5 @@ public class JudgeUtils {
|
|||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package top.hcode.hoj.pojo.entity.judge;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/2/6 14:42
|
||||
* @Description:
|
||||
*/
|
||||
@Data
|
||||
public class CompileDTO {
|
||||
|
||||
/**
|
||||
* 编译的源代码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 编译的源代码相关的题目id
|
||||
*/
|
||||
private Long pid;
|
||||
|
||||
/**
|
||||
* 编译的源代码所选语言
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 调用判题机的凭证
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 编译所需的额外文件,key:文件名,value:文件内容
|
||||
*/
|
||||
private HashMap<String,String> extraFiles;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package top.hcode.hoj.pojo.entity.judge;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/2/6 14:42
|
||||
* @Description:
|
||||
*/
|
||||
@Data
|
||||
public class CompileSpj {
|
||||
|
||||
private String spjSrc;
|
||||
|
||||
private Long pid;
|
||||
|
||||
private String spjLanguage;
|
||||
|
||||
private String token;
|
||||
}
|
|
@ -42,6 +42,9 @@ public class Problem implements Serializable {
|
|||
@ApiModelProperty(value = "0为ACM,1为OI")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "default,spj,interactive")
|
||||
private String judgeMode;
|
||||
|
||||
@ApiModelProperty(value = "单位ms")
|
||||
private Integer timeLimit;
|
||||
|
||||
|
@ -78,20 +81,28 @@ public class Problem implements Serializable {
|
|||
@ApiModelProperty(value = "默认为1公开,2为私有,3为比赛中")
|
||||
private Integer auth;
|
||||
|
||||
@ApiModelProperty(value = "当该题目为io题目时的分数")
|
||||
@ApiModelProperty(value = "当该题目为oi题目时的分数")
|
||||
private Integer ioScore;
|
||||
|
||||
@ApiModelProperty(value = "该题目对应的相关提交代码,用户是否可用分享")
|
||||
private Boolean codeShare;
|
||||
|
||||
@ApiModelProperty(value = "特判程序的代码 空代表无特判")
|
||||
@ApiModelProperty(value = "特判程序或交互程序的代码")
|
||||
@TableField(value="spj_code",updateStrategy = FieldStrategy.IGNORED)
|
||||
private String spjCode;
|
||||
|
||||
@ApiModelProperty(value = "特判程序的语言")
|
||||
@ApiModelProperty(value = "特判程序或交互程序的语言")
|
||||
@TableField(value="spj_language",updateStrategy = FieldStrategy.IGNORED)
|
||||
private String spjLanguage;
|
||||
|
||||
@ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
|
||||
@TableField(value="user_extra_file",updateStrategy = FieldStrategy.IGNORED)
|
||||
private String userExtraFile;
|
||||
|
||||
@ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
|
||||
@TableField(value="judge_extra_file",updateStrategy = FieldStrategy.IGNORED)
|
||||
private String judgeExtraFile;
|
||||
|
||||
@ApiModelProperty(value = "是否默认去除用户代码的每行末尾空白符")
|
||||
private Boolean isRemoveEndBlank;
|
||||
|
||||
|
|
|
@ -689,7 +689,7 @@ footer h1 {
|
|||
box-shadow: inset 0 0 12px rgb(219 219 219);
|
||||
}
|
||||
.markdown-body p {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
line-height: 1.8;
|
||||
|
|
|
@ -1007,6 +1007,11 @@ const adminApi = {
|
|||
data
|
||||
})
|
||||
},
|
||||
compileInteractive(data){
|
||||
return ajax('/api/admin/problem/compile-interactive', 'post', {
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
admin_addTag (data) {
|
||||
return ajax('/api/admin/tag', 'post', {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tit .accordion {
|
||||
.accordion {
|
||||
border: 1px solid #eaeefb;
|
||||
}
|
||||
.accordion header {
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="accordion">
|
||||
<el-tag
|
||||
:key="index"
|
||||
v-for="(value, key, index) in files"
|
||||
closable
|
||||
class="file"
|
||||
:disable-transitions="false"
|
||||
@close="deleteFile(key)"
|
||||
@click="openFileDialog(key, value)"
|
||||
>
|
||||
{{ key }}
|
||||
</el-tag>
|
||||
|
||||
<el-button
|
||||
class="button-new-file"
|
||||
size="small"
|
||||
@click="openFileDialog('', '')"
|
||||
>+ New File</el-button
|
||||
>
|
||||
|
||||
<el-dialog
|
||||
:width="dialogWith"
|
||||
:visible.sync="upsertFileDialogVisible"
|
||||
@close-on-click-modal="false"
|
||||
>
|
||||
<el-form>
|
||||
<el-form-item :label="$t('m.File_Name')" required>
|
||||
<el-input
|
||||
v-model="fileName"
|
||||
size="small"
|
||||
placeholder="******.h"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('m.File_Content')" required>
|
||||
<code-mirror v-model="fileContent"></code-mirror>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="text-align:center;margin-top:10px">
|
||||
<el-button type="primary" @click="upsertFile"
|
||||
>{{ $t('m.Save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CodeMirror from '@/components/admin/CodeMirror.vue';
|
||||
import myMessage from '@/common/message';
|
||||
export default {
|
||||
name: 'Accordion',
|
||||
components: {
|
||||
CodeMirror,
|
||||
},
|
||||
props: {
|
||||
files: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
let screenWidth = window.screen.width;
|
||||
if (screenWidth < 768) {
|
||||
this.dialogWith = '100%';
|
||||
} else {
|
||||
this.dialogWith = '70%';
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
upsertFileDialogVisible: false,
|
||||
upsertTagLoading: false,
|
||||
fileName: '',
|
||||
fileOldName: '',
|
||||
fileContent: '',
|
||||
dialogWith: '70%',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteFile(fileName) {
|
||||
this.$confirm(this.$i18n.t('m.Delete_Extra_File_Tips'), 'Tips', {
|
||||
type: 'warning',
|
||||
}).then(
|
||||
() => {
|
||||
this.$emit('deleteFile', this.type, fileName);
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
},
|
||||
openFileDialog(name, content) {
|
||||
this.fileName = name;
|
||||
this.fileContent = content;
|
||||
this.fileOldName = name;
|
||||
this.upsertFileDialogVisible = true;
|
||||
},
|
||||
upsertFile() {
|
||||
if (!this.fileName) {
|
||||
myMessage.error(
|
||||
this.$i18n.t('m.File_Name') + ' ' + this.$i18n.t('m.is_required')
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!this.fileContent) {
|
||||
myMessage.error(
|
||||
this.$i18n.t('m.File_Content') + ' ' + this.$i18n.t('m.is_required')
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.$emit(
|
||||
'upsertFile',
|
||||
this.type,
|
||||
this.fileName,
|
||||
this.fileOldName,
|
||||
this.fileContent
|
||||
);
|
||||
this.upsertFileDialogVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.accordion {
|
||||
border: 1px solid #eaeefb;
|
||||
}
|
||||
.file {
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button-new-file {
|
||||
height: 32px;
|
||||
line-height: 30px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin: 10px;
|
||||
}
|
||||
/deep/.CodeMirror-scroll {
|
||||
max-height: 300px;
|
||||
}
|
||||
</style>
|
|
@ -32,7 +32,7 @@ export default {
|
|||
return {
|
||||
currentValue: '',
|
||||
options: {
|
||||
mode: 'text/x-csrc',
|
||||
mode: 'text/x-c++src',
|
||||
lineNumbers: true,
|
||||
lineWrapping: false,
|
||||
theme: 'solarized',
|
||||
|
@ -56,11 +56,11 @@ export default {
|
|||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'C',
|
||||
default: '',
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'text/x-csrc',
|
||||
default: 'text/x-c++src',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -102,6 +102,6 @@ export default {
|
|||
|
||||
.CodeMirror-scroll {
|
||||
min-height: 300px;
|
||||
max-height: 1000px;
|
||||
max-height: 600px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<template>
|
||||
<div class="panel" :class="{'small': small}">
|
||||
<header>
|
||||
<div class="title">
|
||||
<template v-if="$slots.title">
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{title}}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="header_right">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Panel',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.panel {
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
|
||||
&.small {
|
||||
max-width: 830px;
|
||||
min-width: 700px;
|
||||
margin-left: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
header {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
> .title {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
border-color: #ddd;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.025em;
|
||||
height: 60px;
|
||||
line-height: 45px;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
> .header_right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
.body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.panel-options {
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
height: 50px;
|
||||
button {
|
||||
margin-top: 18px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
> .page {
|
||||
position: absolute;
|
||||
right:20px;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -168,7 +168,6 @@ export default {
|
|||
showCursorWhenSelecting: true,
|
||||
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true },
|
||||
// extraKeys: { Ctrl: 'autocomplete' }, //自定义快捷键
|
||||
autoFocus: true,
|
||||
matchBrackets: true, //括号匹配
|
||||
indentUnit: 4, //一个块(编辑语言中的含义)应缩进多少个空格
|
||||
styleActiveLine: true,
|
||||
|
|
|
@ -194,13 +194,18 @@ export const m = {
|
|||
Example_Input:'Example Input',
|
||||
Example_Output:'Example Output',
|
||||
Add_Example: 'Add Example',
|
||||
Special_Judge: 'Special Judge',
|
||||
Special_Judge_Code: 'Special Judge Code',
|
||||
Special_Judge_Tips1:'Why use special judge?',
|
||||
Special_Judge_Tips2:'The output required by the problem may not be unique, and different results are allowed.',
|
||||
Special_Judge_Tips3:'The output within a certain precision range is acceptable.',
|
||||
Judge_Mode:'Judge Mode',
|
||||
General_Judge:'General Judge',
|
||||
Special_Judge:'Special Judge',
|
||||
Interactive_Judge:'Interactive Judge',
|
||||
Special_Judge_Code: 'Special Judge Program Code',
|
||||
Interactive_Judge_Code:'Interactive Judge Program Code',
|
||||
General_Judge_Mode_Tips:'1. General Judge: the contestant program reads the problem standard input file, executes the code logic to obtain the contestant\'s output, and compares the contents of the problem standard output file to obtain the problem judgment result',
|
||||
Special_Judge_Mode_Tips:'2. Special Judge: the output results required by the problem may not be unique, and different results are allowed. Therefore, a special program is needed to read standard output, player output and standard input, and compare them to obtain the final judgment result',
|
||||
Interactive_Judge_Mode_Tips:'3. Interactive Judge: the standard output of the interactive program is written to the standard input of the player program through the interactive channel, and the standard output of the player program is written to the standard input of the interactive program through the interactive channel. Both need to flush the output buffer',
|
||||
Use_Special_Judge: 'Use Special Judge',
|
||||
SPJ_language: 'SPJ language',
|
||||
SPJ_Language: 'SPJ Program Language',
|
||||
Interactive_Language:'Interactive Program Langugae',
|
||||
Compile: 'Compile',
|
||||
Code_Template: 'Code Template',
|
||||
Type: 'Type',
|
||||
|
@ -228,8 +233,13 @@ export const m = {
|
|||
is_required:'is required!',
|
||||
Score_must_be_greater_than_or_equal_to_0:'Score must be greater than or equal to 0!',
|
||||
Score_must_be_an_integer:'Score must be an integer!',
|
||||
Spj_Code:'Spj Code',
|
||||
Spj_Code_not_Compile_Success:'Spj Code was not compiled successfully, please compile again!',
|
||||
Spj_Or_Interactive_Code:'Spj Or Interactive Code',
|
||||
Spj_Or_Interactive_Code_not_Compile_Success:'Spj Or Interactive Code was not compiled successfully, please compile again!',
|
||||
Judge_Extra_File:'Judge Extra File',
|
||||
Judge_Extra_File_Tips1:'1. User Program: Provide additional library files for user program',
|
||||
Judge_Extra_File_Tips2:'2. Special Or Interactive Program: Provide additional library files for special or interactive programs',
|
||||
User_Program:'User Program',
|
||||
SPJ_Or_Interactive_Program:'Special Or Interactive Program',
|
||||
|
||||
|
||||
// /views/admin/problem/tag
|
||||
|
@ -342,4 +352,9 @@ export const m = {
|
|||
Content:'Content',
|
||||
Report_Content:'Report Content',
|
||||
The_number_of_discussions_selected_cannot_be_empty:'The number of discussions selected cannot be empty',
|
||||
|
||||
// components/admin/AddExtraFile.vue
|
||||
Delete_Extra_File_Tips:'Are you sure you want to delete this extra file?',
|
||||
File_Name:'File Name',
|
||||
File_Content:'File Content'
|
||||
}
|
||||
|
|
|
@ -193,13 +193,17 @@ export const m = {
|
|||
Example_Input:'样例输入',
|
||||
Example_Output:'样例输出',
|
||||
Add_Example: '添加样例',
|
||||
Special_Judge: '特殊判题',
|
||||
Special_Judge_Code:'特殊判题代码',
|
||||
Special_Judge_Tips1:'为什么要使用特殊判题?',
|
||||
Special_Judge_Tips2:'题目要求的输出结果可能不唯一,允许不同结果存在。',
|
||||
Special_Judge_Tips3:'题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以。例如题目要求保留几位小数,输出结果后几位小数不相同也是正确的。',
|
||||
Use_Special_Judge: '使用特殊判题',
|
||||
SPJ_language: 'SPJ语言',
|
||||
Judge_Mode:'判题模式',
|
||||
General_Judge:'普通判题',
|
||||
Special_Judge:'特殊判题',
|
||||
Interactive_Judge:'交互判题',
|
||||
Special_Judge_Code:'特殊判题程序代码',
|
||||
Interactive_Judge_Code:'交互判题程序代码',
|
||||
General_Judge_Mode_Tips:'1. 普通判题:选手程序读取题目标准输入文件,执行代码逻辑得到选手输出,对比题目标准输出文件内容得到判题结果',
|
||||
Special_Judge_Mode_Tips:'2. 特殊判题:题目要求的输出结果可能不唯一,允许不同结果存在,所以需要一个特殊程序读取标准输出、选手输出和标准输入,进行对比得出最终判题结果',
|
||||
Interactive_Judge_Mode_Tips:'3. 交互判题:交互程序的标准输出通过交互通道写到选手程序标准输入,选手程序的标准输出通过交互通道写到交互程序的标准输入,两者需要刷新输出缓冲区',
|
||||
Interactive_Language:'交互判题程序语言',
|
||||
SPJ_Language: '特殊判题程序语言',
|
||||
Compile: '编译',
|
||||
Code_Template: '代码模板',
|
||||
Type: '类型',
|
||||
|
@ -227,9 +231,13 @@ export const m = {
|
|||
is_required:'不能为空!',
|
||||
Score_must_be_greater_than_or_equal_to_0:'分数必须大于0!',
|
||||
Score_must_be_an_integer:'分数必须是整数!',
|
||||
Spj_Code:'Spj代码',
|
||||
Spj_Code_not_Compile_Success:'Spj代码没有编译成功,请重新编译!',
|
||||
|
||||
Spj_Or_Interactive_Code:'Spj或交互程序的代码',
|
||||
Spj_Or_Interactive_Code_not_Compile_Success:'Spj或交互程序的代码没有编译成功,请重新编译!',
|
||||
Judge_Extra_File:'评测额外文件',
|
||||
Judge_Extra_File_Tips1:'1. 选手程序:给选手程序提供额外的库文件',
|
||||
Judge_Extra_File_Tips2:'2. 特殊或交互程序:给特殊或交互程序提供额外的库文件',
|
||||
User_Program:'选手程序',
|
||||
SPJ_Or_Interactive_Program:'特殊或交互程序',
|
||||
|
||||
// /views/admin/problem/tag
|
||||
Admin_Tag:'标签管理',
|
||||
|
@ -340,4 +348,9 @@ export const m = {
|
|||
Content:'内容',
|
||||
Report_Content:'举报内容',
|
||||
The_number_of_discussions_selected_cannot_be_empty:'勾选的讨论不能为空',
|
||||
|
||||
// components/admin/AddExtraFile.vue
|
||||
Delete_Extra_File_Tips:'你是否确定要删除该额外文件?',
|
||||
File_Name:'文件名字',
|
||||
File_Content:'文件内容'
|
||||
}
|
||||
|
|
|
@ -138,6 +138,11 @@
|
|||
<Editor :value.sync="problem.output"></Editor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
|
||||
<Editor :value.sync="problem.hint"></Editor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
@ -159,6 +164,20 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="4" :xs="24">
|
||||
<el-form-item :label="$t('m.Type')">
|
||||
<el-radio-group
|
||||
v-model="problem.type"
|
||||
:disabled="disableRuleType || problem.isRemote"
|
||||
@change="problemTypeChange"
|
||||
>
|
||||
<el-radio :label="0">ACM</el-radio>
|
||||
<el-radio :label="1">OI</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="4" :xs="24">
|
||||
<el-form-item :label="$t('m.Code_Shareable')">
|
||||
<el-switch
|
||||
|
@ -169,7 +188,8 @@
|
|||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="16" :xs="24">
|
||||
|
||||
<el-col :md="12" :xs="24">
|
||||
<el-form-item :label="$t('m.Tags')" required>
|
||||
<el-tag
|
||||
v-for="tag in problemTags"
|
||||
|
@ -300,30 +320,87 @@
|
|||
>{{ $t('m.Add_Example') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<template v-if="!problem.isRemote">
|
||||
<div class="panel-title home-title">
|
||||
{{ $t('m.Special_Judge') }}
|
||||
{{ $t('m.Judge_Extra_File') }}
|
||||
<el-popover placement="right" trigger="hover">
|
||||
<p>{{ $t('m.Special_Judge_Tips1') }}</p>
|
||||
<p>1. {{ $t('m.Special_Judge_Tips2') }}</p>
|
||||
<p>2. {{ $t('m.Special_Judge_Tips3') }}</p>
|
||||
<p>{{ $t('m.Judge_Extra_File_Tips1') }}</p>
|
||||
<p>{{ $t('m.Judge_Extra_File_Tips2') }}</p>
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="12" :xs="24">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="addUserExtraFile">{{
|
||||
$t('m.User_Program')
|
||||
}}</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="addUserExtraFile">
|
||||
<AddExtraFile
|
||||
:files.sync="userExtraFile"
|
||||
type="user"
|
||||
@upsertFile="upsertFile"
|
||||
@deleteFile="deleteFile"
|
||||
></AddExtraFile>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="12" :xs="24">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="addJudgeExtraFile">{{
|
||||
$t('m.SPJ_Or_Interactive_Program')
|
||||
}}</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="addJudgeExtraFile">
|
||||
<AddExtraFile
|
||||
:files.sync="judgeExtraFile"
|
||||
type="judge"
|
||||
@upsertFile="upsertFile"
|
||||
@deleteFile="deleteFile"
|
||||
></AddExtraFile>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<template v-if="!problem.isRemote">
|
||||
<div class="panel-title home-title">
|
||||
{{ $t('m.Judge_Mode') }}
|
||||
<el-popover placement="right" trigger="hover">
|
||||
<p>{{ $t('m.General_Judge_Mode_Tips') }}</p>
|
||||
<p>{{ $t('m.Special_Judge_Mode_Tips') }}</p>
|
||||
<p>{{ $t('m.Interactive_Judge_Mode_Tips') }}</p>
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-form-item label="" :error="error.spj">
|
||||
<el-col :span="24">
|
||||
<el-checkbox
|
||||
v-model="problem.spj"
|
||||
@click.native.prevent="switchSpj()"
|
||||
>{{ $t('m.Use_Special_Judge') }}</el-checkbox
|
||||
>
|
||||
<el-radio-group v-model="problem.judgeMode" @change="switchMode">
|
||||
<el-radio label="default">{{ $t('m.General_Judge') }}</el-radio>
|
||||
<el-radio label="spj">{{ $t('m.Special_Judge') }}</el-radio>
|
||||
<el-radio label="interactive">{{
|
||||
$t('m.Interactive_Judge')
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="problem.spj">
|
||||
<Accordion :title="$t('m.Special_Judge_Code')">
|
||||
<el-form-item v-if="problem.judgeMode != 'default'">
|
||||
<Accordion
|
||||
:title="
|
||||
problem.judgeMode == 'spj'
|
||||
? $t('m.Special_Judge_Code')
|
||||
: $t('m.Interactive_Judge_Code')
|
||||
"
|
||||
>
|
||||
<template slot="header">
|
||||
<span style="margin-right:5px;"
|
||||
>{{ $t('m.SPJ_language') }}:</span
|
||||
>{{
|
||||
problem.judgeMode == 'spj'
|
||||
? $t('m.SPJ_Language')
|
||||
: $t('m.Interactive_Language')
|
||||
}}:</span
|
||||
>
|
||||
<el-radio-group v-model="problem.spjLanguage">
|
||||
<el-tooltip
|
||||
|
@ -355,11 +432,8 @@
|
|||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
|
||||
<Editor :value.sync="problem.hint"></Editor>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('m.Code_Template')">
|
||||
<div class="panel-title home-title">{{ $t('m.Code_Template') }}</div>
|
||||
<el-form-item>
|
||||
<el-row>
|
||||
<el-col
|
||||
:span="24"
|
||||
|
@ -376,21 +450,6 @@
|
|||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item :label="$t('m.Type')">
|
||||
<el-radio-group
|
||||
v-model="problem.type"
|
||||
:disabled="disableRuleType || problem.isRemote"
|
||||
@change="problemTypeChange"
|
||||
>
|
||||
<el-radio :label="0">ACM</el-radio>
|
||||
<el-radio :label="1">OI</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" v-if="!problem.isRemote">
|
||||
<div class="panel-title home-title">
|
||||
{{ $t('m.Judge_Samples') }}
|
||||
|
@ -582,11 +641,13 @@ import myMessage from '@/common/message';
|
|||
import { PROBLEM_LEVEL_RESERVE } from '@/common/constants';
|
||||
const Editor = () => import('@/components/admin/Editor.vue');
|
||||
const Accordion = () => import('@/components/admin/Accordion.vue');
|
||||
const AddExtraFile = () => import('@/components/admin/AddExtraFile.vue');
|
||||
const CodeMirror = () => import('@/components/admin/CodeMirror.vue');
|
||||
export default {
|
||||
name: 'Problem',
|
||||
components: {
|
||||
Accordion,
|
||||
AddExtraFile,
|
||||
CodeMirror,
|
||||
Editor,
|
||||
},
|
||||
|
@ -635,7 +696,6 @@ export default {
|
|||
auth: 1,
|
||||
codeShare: true,
|
||||
examples: [], // 题面上的样例输入输出
|
||||
spj: false,
|
||||
spjLanguage: '',
|
||||
spjCode: '',
|
||||
spjCompileOk: false,
|
||||
|
@ -647,6 +707,9 @@ export default {
|
|||
hint: '',
|
||||
source: '',
|
||||
cid: null,
|
||||
judgeMode: 'default',
|
||||
userExtraFile: '',
|
||||
judgeExtraFile: '',
|
||||
},
|
||||
problemTags: [], //指定问题的标签列表
|
||||
problemLanguages: [], //指定问题的编程语言列表
|
||||
|
@ -677,6 +740,10 @@ export default {
|
|||
spjCode: '',
|
||||
spjLanguage: '',
|
||||
},
|
||||
addUserExtraFile: false,
|
||||
addJudgeExtraFile: false,
|
||||
userExtraFile: null,
|
||||
judgeExtraFile: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -723,7 +790,6 @@ export default {
|
|||
auth: 1,
|
||||
codeShare: true,
|
||||
examples: [],
|
||||
spj: false,
|
||||
spjLanguage: '',
|
||||
spjCode: '',
|
||||
spjCompileOk: false,
|
||||
|
@ -735,6 +801,9 @@ export default {
|
|||
hint: '',
|
||||
source: '',
|
||||
cid: null,
|
||||
judgeMode: 'default',
|
||||
userExtraFile: null,
|
||||
judgeExtraFile: null,
|
||||
};
|
||||
|
||||
this.contestID = contestID;
|
||||
|
@ -829,13 +898,11 @@ export default {
|
|||
}[this.routeName];
|
||||
api[funcName](this.pid).then((problemRes) => {
|
||||
let data = problemRes.data.data;
|
||||
(data.spjCompileOk = false),
|
||||
(data.uploadTestcaseDir = ''),
|
||||
(data.testCaseScore = []);
|
||||
data.spj = true;
|
||||
data.spjCompileOk = false;
|
||||
data.uploadTestcaseDir = '';
|
||||
data.testCaseScore = [];
|
||||
if (!data.spjCode) {
|
||||
data.spjCode = '';
|
||||
data.spj = false;
|
||||
}
|
||||
data.spjLanguage = data.spjLanguage || 'C';
|
||||
this.spjRecord.spjLanguage = data.spjLanguage;
|
||||
|
@ -844,7 +911,14 @@ export default {
|
|||
this.problem['examples'] = utils.stringToExamples(data.examples);
|
||||
this.problem['examples'][0]['isOpen'] = true;
|
||||
this.testCaseUploaded = true;
|
||||
|
||||
if (this.problem.userExtraFile) {
|
||||
this.addUserExtraFile = true;
|
||||
this.userExtraFile = JSON.parse(this.problem.userExtraFile);
|
||||
}
|
||||
if (this.problem.judgeExtraFile) {
|
||||
this.addJudgeExtraFile = true;
|
||||
this.judgeExtraFile = JSON.parse(this.problem.judgeExtraFile);
|
||||
}
|
||||
api
|
||||
.admin_getProblemCases(this.pid, this.problem.isUploadCase)
|
||||
.then((res) => {
|
||||
|
@ -891,7 +965,7 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
switchSpj() {
|
||||
switchMode(mode) {
|
||||
if (this.testCaseUploaded) {
|
||||
this.$confirm(this.$i18n.t('m.Change_Judge_Method'), 'Tips', {
|
||||
confirmButtonText: this.$i18n.t('m.OK'),
|
||||
|
@ -899,12 +973,12 @@ export default {
|
|||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
this.problem.spj = !this.problem.spj;
|
||||
this.problem.judgeMode = mode;
|
||||
this.resetTestCase();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
this.problem.spj = !this.problem.spj;
|
||||
this.problem.judgeMode = mode;
|
||||
}
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
|
@ -965,6 +1039,34 @@ export default {
|
|||
);
|
||||
},
|
||||
|
||||
deleteFile(type, name) {
|
||||
if (type == 'user') {
|
||||
this.$delete(this.userExtraFile, name);
|
||||
} else {
|
||||
this.$delete(this.judgeExtraFile, name);
|
||||
}
|
||||
},
|
||||
|
||||
upsertFile(type, name, oldname, content) {
|
||||
if (type == 'user') {
|
||||
if (oldname && oldname != name) {
|
||||
this.$delete(this.userExtraFile, oldname);
|
||||
}
|
||||
if (!this.userExtraFile) {
|
||||
this.userExtraFile = {};
|
||||
}
|
||||
this.userExtraFile[name] = content;
|
||||
} else {
|
||||
if (oldname && oldname != name) {
|
||||
this.$delete(this.judgeExtraFile, name);
|
||||
}
|
||||
if (!this.judgeExtraFile) {
|
||||
this.judgeExtraFile = {};
|
||||
}
|
||||
this.judgeExtraFile[name] = content;
|
||||
}
|
||||
},
|
||||
|
||||
problemTypeChange(type) {
|
||||
if (type == 1) {
|
||||
let length = this.problemSamples.length;
|
||||
|
@ -1036,7 +1138,7 @@ export default {
|
|||
fileList[i].score = averSorce;
|
||||
}
|
||||
}
|
||||
if (!fileList[i].output && this.problem.spj) {
|
||||
if (!fileList[i].output) {
|
||||
fileList[i].output = '-';
|
||||
}
|
||||
fileList[i].pid = this.problem.id;
|
||||
|
@ -1052,11 +1154,16 @@ export default {
|
|||
compileSPJ() {
|
||||
let data = {
|
||||
pid: this.problem.id,
|
||||
spjSrc: this.problem.spjCode,
|
||||
spjLanguage: this.problem.spjLanguage,
|
||||
code: this.problem.spjCode,
|
||||
language: this.problem.spjLanguage,
|
||||
extraFiles: this.judgeExtraFile,
|
||||
};
|
||||
this.loadingCompile = true;
|
||||
api.compileSPJ(data).then(
|
||||
let apiMethodName = 'compileSPJ';
|
||||
if (this.problem.judgeMode == 'interactive') {
|
||||
apiMethodName = 'compileInteractive';
|
||||
}
|
||||
api[apiMethodName](data).then(
|
||||
(res) => {
|
||||
this.loadingCompile = false;
|
||||
this.problem.spjCompileOk = true;
|
||||
|
@ -1218,14 +1325,21 @@ export default {
|
|||
myMessage.error(this.error.tags);
|
||||
return;
|
||||
}
|
||||
let isChangeModeCode =
|
||||
this.spjRecord.spjLanguage != this.problem.spjLanguage ||
|
||||
this.spjRecord.spjCode != this.problem.spjCode;
|
||||
|
||||
if (this.problem.spj) {
|
||||
if (this.problem.judgeMode != 'default') {
|
||||
if (!this.problem.spjCode) {
|
||||
this.error.spj =
|
||||
this.$i18n.t('m.Spj_Code') + ' ' + this.$i18n.t('m.is_required');
|
||||
this.$i18n.t('m.Spj_Or_Interactive_Code') +
|
||||
' ' +
|
||||
this.$i18n.t('m.is_required');
|
||||
myMessage.error(this.error.spj);
|
||||
} else if (!this.problem.spjCompileOk) {
|
||||
this.error.spj = this.$i18n.t('m.Spj_Code_not_Compile_Success');
|
||||
} else if (!this.problem.spjCompileOk && isChangeModeCode) {
|
||||
this.error.spj = this.$i18n.t(
|
||||
'm.Spj_Or_Interactive_Code_not_Compile_Success'
|
||||
);
|
||||
}
|
||||
if (this.error.spj) {
|
||||
myMessage.error(this.error.spj);
|
||||
|
@ -1294,21 +1408,31 @@ export default {
|
|||
}
|
||||
|
||||
let problemDto = {}; // 上传给后台的数据
|
||||
if (this.problem.spj) {
|
||||
if (
|
||||
this.spjRecord.spjLanguage != this.problem.spjLanguage ||
|
||||
this.spjRecord.spjCode != this.problem.spjCode
|
||||
) {
|
||||
problemDto['changeSpj'] = true;
|
||||
if (this.problem.judgeMode != 'default') {
|
||||
if (isChangeModeCode) {
|
||||
problemDto['changeModeCode'] = true;
|
||||
}
|
||||
} else {
|
||||
// 原本是spj,但现在关闭了
|
||||
// 原本是spj或交互,但现在关闭了
|
||||
if (!this.spjRecord.spjCode) {
|
||||
problemDto['changeSpj'] = true;
|
||||
problemDto['changeModeCode'] = true;
|
||||
this.problem.spjCode = null;
|
||||
this.problem.spjLanguage = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.userExtraFile && Object.keys(this.userExtraFile).length != 0) {
|
||||
this.problem.userExtraFile = JSON.stringify(this.userExtraFile);
|
||||
} else {
|
||||
this.problem.userExtraFile = null;
|
||||
}
|
||||
|
||||
if (this.judgeExtraFile && Object.keys(this.judgeExtraFile).length != 0) {
|
||||
this.problem.judgeExtraFile = JSON.stringify(this.judgeExtraFile);
|
||||
} else {
|
||||
this.problem.judgeExtraFile = null;
|
||||
}
|
||||
|
||||
problemDto['problem'] = Object.assign({}, this.problem); // 深克隆
|
||||
problemDto.problem.examples = utils.examplesToString(
|
||||
this.problem.examples
|
||||
|
@ -1319,7 +1443,7 @@ export default {
|
|||
problemDto['languages'] = problemLanguageList;
|
||||
problemDto['isUploadTestCase'] = this.problem.isUploadCase;
|
||||
problemDto['uploadTestcaseDir'] = this.problem.uploadTestcaseDir;
|
||||
problemDto['isSpj'] = this.problem.spj;
|
||||
problemDto['judgeMode'] = this.problem.judgeMode;
|
||||
|
||||
// 如果选择上传文件,则使用上传后的结果
|
||||
if (this.problem.isUploadCase) {
|
||||
|
|
|
@ -57,7 +57,7 @@ module.exports={
|
|||
port: 8088, // 开发服务器运行端口号
|
||||
proxy: {
|
||||
'/api': { // 以'/api'开头的请求会被代理进行转发
|
||||
target: 'http://localhost:6688', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
|
||||
target: 'https://hdoi.cn', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue