mirror of
https://github.com/MeiK2333/river.git
synced 2025-11-04 14:49:40 +08:00
update
This commit is contained in:
parent
d3ed13ea13
commit
525f566466
@ -4,10 +4,4 @@ version = "0.1.0"
|
||||
authors = ["MeiK <meik2333@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
handlebars = "3.0.1"
|
||||
yaml-rust = "0.4"
|
||||
tempfile = "3"
|
||||
|
||||
@ -1 +0,0 @@
|
||||
3
|
||||
@ -1 +0,0 @@
|
||||
1 2
|
||||
@ -1 +0,0 @@
|
||||
-3
|
||||
@ -1 +0,0 @@
|
||||
-1 -2
|
||||
@ -1 +0,0 @@
|
||||
2147483648
|
||||
@ -1 +0,0 @@
|
||||
2147483647 1
|
||||
@ -1,27 +0,0 @@
|
||||
time_limit: 1000 # ms
|
||||
memory_limit: 65535 # kib
|
||||
test_cases:
|
||||
- in: 1.in
|
||||
ans: 1.ans
|
||||
- in: 2.in
|
||||
ans: 2.ans
|
||||
- in: 3.in
|
||||
ans: 3.ans
|
||||
time_limit: 300
|
||||
real_time_limit: 600
|
||||
memory_limit: 32767
|
||||
judge_type: standard # standard / special
|
||||
# special_judge:
|
||||
# file: special.cpp
|
||||
# language: cpp
|
||||
extra_files:
|
||||
- hello.txt
|
||||
code:
|
||||
file: main.c
|
||||
language: c
|
||||
# code:
|
||||
# file: main.cpp
|
||||
# language: cpp
|
||||
# code:
|
||||
# file: main.py
|
||||
# language: python
|
||||
@ -1 +0,0 @@
|
||||
Hello World!
|
||||
@ -1,8 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
int a, b;
|
||||
scanf("%d %d", &a, &b);
|
||||
printf("%d\n", a + b);
|
||||
return 0;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
int a, b;
|
||||
cin >> a >> b;
|
||||
cout << a + b << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
a, b = input().split()
|
||||
print(int(a) + int(b))
|
||||
@ -1,18 +0,0 @@
|
||||
time_limit: 1000
|
||||
memory_limit: 65535
|
||||
test_cases:
|
||||
- in: data.in
|
||||
ans: data.ans
|
||||
judge_type: standard
|
||||
# code:
|
||||
# file: main.c
|
||||
# language: c
|
||||
# code:
|
||||
# file: main.cpp
|
||||
# language: cpp
|
||||
code:
|
||||
file: main.py
|
||||
language: python
|
||||
# code:
|
||||
# file: main.sh
|
||||
# language: bash
|
||||
@ -1 +0,0 @@
|
||||
Hello World!
|
||||
@ -1,6 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello World!\n");
|
||||
return 0;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
cout << "Hello World!" << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
import time
|
||||
time.sleep(3)
|
||||
print('Hello World!')
|
||||
@ -1 +0,0 @@
|
||||
echo "Hello World!"
|
||||
118
src/config.rs
118
src/config.rs
@ -1,118 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
use super::error::{Error, Result};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LanguageConfig {
|
||||
pub language: String,
|
||||
pub version: String,
|
||||
pub compile_command: String,
|
||||
pub run_command: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for LanguageConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.language, self.version)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub languages: Vec<LanguageConfig>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn language_config_from_name(&self, name: &str) -> Result<LanguageConfig> {
|
||||
for language in &self.languages {
|
||||
if language.language == name {
|
||||
return Ok(language.clone());
|
||||
}
|
||||
}
|
||||
Err(Error::LanguageNotFound(name.to_string()))
|
||||
}
|
||||
fn load_yaml(yaml: &str) -> Result<Config> {
|
||||
let docs = match YamlLoader::load_from_str(yaml) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::YamlScanError(err)),
|
||||
};
|
||||
let doc = &docs[0];
|
||||
|
||||
// 读取语言配置
|
||||
let mut languages: Vec<LanguageConfig> = vec![];
|
||||
let yaml_languages = match &doc["languages"] {
|
||||
Yaml::Array(value) => value,
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 languages 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,languages 字段的类型应该为 Array".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
for yaml_language in yaml_languages {
|
||||
let language = match &yaml_language["language"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,language 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let version = match &yaml_language["version"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"version 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let compile_command = match &yaml_language["compile_command"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"compile_command 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let run_command = match &yaml_language["run_command"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"run_command 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
languages.push(LanguageConfig {
|
||||
language,
|
||||
version,
|
||||
compile_command,
|
||||
run_command,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Config { languages })
|
||||
}
|
||||
pub fn load_from_file(filename: &str) -> Result<Config> {
|
||||
let contents = match fs::read_to_string(filename) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let config = Config::load_yaml(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
pub fn default() -> Result<Config> {
|
||||
let config_yaml = include_str!("default.yml");
|
||||
let config = Config::load_yaml(config_yaml)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
languages:
|
||||
- language: c
|
||||
version: 7.5.0
|
||||
compile_command: /usr/bin/gcc {{filename}} -o a.out -O2
|
||||
run_command: ./a.out
|
||||
- language: cpp
|
||||
version: 7.5.0
|
||||
compile_command: /usr/bin/g++ {{filename}} -o a.out -O2
|
||||
run_command: ./a.out
|
||||
- language: bash
|
||||
version: 4.4.20
|
||||
compile_command: "/bin/echo Hello World!"
|
||||
run_command: /bin/bash {{filename}}
|
||||
- language: python
|
||||
version: 3.6.9
|
||||
compile_command: /usr/bin/python3 -m compileall {{filename}}
|
||||
run_command: /usr/bin/python3 {{filename}}
|
||||
84
src/error.rs
84
src/error.rs
@ -1,84 +0,0 @@
|
||||
use handlebars::RenderError;
|
||||
use libc::strerror;
|
||||
use std::ffi::{CStr, NulError, OsString};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::time;
|
||||
use yaml_rust::ScanError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
YamlScanError(ScanError),
|
||||
YamlParseError(String),
|
||||
LanguageNotFound(String),
|
||||
ReadFileError(String, Option<i32>),
|
||||
|
||||
UnknownJudgeType(String),
|
||||
PathJoinError,
|
||||
|
||||
StringToCStringError(NulError),
|
||||
TemplateRenderError(RenderError),
|
||||
LanguageConfigError(String),
|
||||
CreateTempDirError(io::Error),
|
||||
// CloseTempDirError(io::Error),
|
||||
CopyFileError(io::Error),
|
||||
OsStringToStringError(OsString),
|
||||
ForkError(Option<i32>),
|
||||
WaitError(Option<i32>),
|
||||
SystemTimeError(time::SystemTimeError),
|
||||
RemoveFileError(String, Option<i32>),
|
||||
|
||||
OpenFileError(String, Option<i32>),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub fn errno_str(errno: Option<i32>) -> String {
|
||||
match errno {
|
||||
Some(no) => {
|
||||
let stre = unsafe { strerror(no) };
|
||||
let c_str: &CStr = unsafe { CStr::from_ptr(stre) };
|
||||
c_str.to_str().unwrap().to_string()
|
||||
}
|
||||
_ => "Unknown Error!".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::YamlScanError(ref err) => {
|
||||
let _ = write!(f, "YamlScanError: ");
|
||||
err.fmt(f)
|
||||
}
|
||||
Error::YamlParseError(ref err) => write!(f, "YamlParseError: {}", err),
|
||||
Error::LanguageNotFound(ref lang) => write!(f, "LanguageNotFound: {}", lang),
|
||||
Error::ReadFileError(ref filename, errno) => {
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "ReadFileError: `{}` {}", filename, reason)
|
||||
}
|
||||
Error::OpenFileError(ref filename, errno) => {
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "OpenFileError: `{}` {}", filename, reason)
|
||||
}
|
||||
Error::UnknownJudgeType(ref judge_type) => {
|
||||
write!(f, "UnknownJudgeType: {}", judge_type)
|
||||
}
|
||||
Error::PathJoinError => write!(f, "PathJoinError"),
|
||||
Error::ForkError(errno) => {
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "ForkError: {}", reason)
|
||||
}
|
||||
Error::WaitError(errno) => {
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "WaitError: {}", reason)
|
||||
}
|
||||
Error::RemoveFileError(ref filename, errno) => {
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "RemoveFileError: `{}` {}", filename, reason)
|
||||
}
|
||||
_ => write!(f, "{:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
286
src/judger.rs
286
src/judger.rs
@ -1,286 +0,0 @@
|
||||
use super::config;
|
||||
use super::error::{Error, Result};
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
pub struct TestCase {
|
||||
pub index: u32,
|
||||
pub input_file: String,
|
||||
pub output_file: String,
|
||||
pub answer_file: String,
|
||||
pub cpu_time_limit: u32,
|
||||
pub real_time_limit: u32,
|
||||
pub memory_limit: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for TestCase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"index: {}
|
||||
input_file: {}
|
||||
answer_file: {}
|
||||
time_limit: {}
|
||||
memory_limit: {}
|
||||
",
|
||||
self.index, self.input_file, self.answer_file, self.cpu_time_limit, self.memory_limit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum JudgeType {
|
||||
Standard,
|
||||
Special,
|
||||
}
|
||||
|
||||
pub struct Code {
|
||||
pub file: String,
|
||||
pub language: config::LanguageConfig,
|
||||
}
|
||||
|
||||
pub struct JudgeConfig {
|
||||
pub config_dir: String,
|
||||
pub tests: Vec<TestCase>,
|
||||
pub judge_type: JudgeType,
|
||||
pub extra_files: Vec<String>,
|
||||
pub code: Code,
|
||||
}
|
||||
|
||||
impl JudgeConfig {
|
||||
fn load_yaml(global_config: &config::Config, yaml: &str, path: &str) -> Result<JudgeConfig> {
|
||||
let docs = match YamlLoader::load_from_str(yaml) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::YamlScanError(err)),
|
||||
};
|
||||
let doc = &docs[0];
|
||||
|
||||
let default_time_limit = match &doc["time_limit"] {
|
||||
Yaml::Integer(value) => value.clone() as u32,
|
||||
Yaml::BadValue => 1000,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,time_limit 字段的类型应该为 Integer".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let default_memory_limit = match &doc["memory_limit"] {
|
||||
Yaml::Integer(value) => value.clone() as u32,
|
||||
Yaml::BadValue => 65535,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,memory_limit 字段的类型应该为 Integer".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let test_cases = match &doc["test_cases"] {
|
||||
Yaml::Array(value) => value,
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 test_cases 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"test_cases 字段的类型应该为 Array".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut tests: Vec<TestCase> = vec![];
|
||||
// Yaml 解析库没有实现 enumerate 方法,因此此处使用 index 进行计数
|
||||
let mut index = 1;
|
||||
for case in test_cases {
|
||||
let cpu_time_limit = match &case["time_limit"] {
|
||||
Yaml::Integer(value) => value.clone() as u32,
|
||||
Yaml::BadValue => default_time_limit,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,time_limit 字段的类型应该为 Integer".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let real_time_limit = match &case["real_time_limit"] {
|
||||
Yaml::Integer(value) => value.clone() as u32,
|
||||
Yaml::BadValue => cpu_time_limit * 2 + 5000,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,real_time_limit 字段的类型应该为 Integer".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let memory_limit = match &case["memory_limit"] {
|
||||
Yaml::Integer(value) => value.clone() as u32,
|
||||
Yaml::BadValue => default_memory_limit,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,memory_limit 字段的类型应该为 Integer".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let input_file = match &case["in"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 test_cases::in 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"test_cases::in 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let answer_file = match &case["ans"] {
|
||||
Yaml::String(value) => value.clone(),
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 test_cases::ans 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"test_cases::ans 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let output_file = "output.txt".to_string();
|
||||
tests.push(TestCase {
|
||||
index,
|
||||
cpu_time_limit,
|
||||
real_time_limit,
|
||||
memory_limit,
|
||||
input_file,
|
||||
output_file,
|
||||
answer_file,
|
||||
});
|
||||
index += 1;
|
||||
}
|
||||
let judge_type = match &doc["judge_type"] {
|
||||
Yaml::String(value) => {
|
||||
if value == "standard" {
|
||||
JudgeType::Standard
|
||||
} else if value == "special" {
|
||||
JudgeType::Special
|
||||
} else {
|
||||
return Err(Error::UnknownJudgeType(value.clone()));
|
||||
}
|
||||
}
|
||||
Yaml::BadValue => JudgeType::Standard,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"judge_type 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let default_extra_files: Vec<Yaml> = vec![];
|
||||
let extra_files_yaml = match &doc["extra_files"] {
|
||||
Yaml::Array(value) => value,
|
||||
Yaml::BadValue => &default_extra_files,
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"extra_files 字段的类型应该为 Array".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut extra_files: Vec<String> = vec![];
|
||||
for file in extra_files_yaml {
|
||||
let name = match &file {
|
||||
Yaml::String(value) => value.clone(),
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 extra_files 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"extra_files 字段的类型应该为 Array<String>".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
extra_files.push(name);
|
||||
}
|
||||
let code = match &doc["code"] {
|
||||
Yaml::Hash(value) => {
|
||||
let file = match value.get(&Yaml::from_str("file")) {
|
||||
Some(Yaml::String(val)) => val.clone(),
|
||||
Some(Yaml::BadValue) => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 code::file 字段".to_string(),
|
||||
))
|
||||
}
|
||||
None => {
|
||||
return Err(Error::YamlParseError(
|
||||
"必须提供 code::file 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"code::file 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let language = match value.get(&Yaml::from_str("language")) {
|
||||
Some(Yaml::String(val)) => match global_config.language_config_from_name(val) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Err(Error::LanguageNotFound(val.clone())),
|
||||
},
|
||||
Some(Yaml::BadValue) => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 code::language 字段".to_string(),
|
||||
))
|
||||
}
|
||||
None => {
|
||||
return Err(Error::YamlParseError(
|
||||
"必须提供 code::language 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"code::language 字段的类型应该为 String".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
Code { file, language }
|
||||
}
|
||||
Yaml::BadValue => {
|
||||
return Err(Error::YamlParseError(
|
||||
"解析错误,无法解析到 code 字段".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::YamlParseError(
|
||||
"code 字段的类型应该为 Hash".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(JudgeConfig {
|
||||
config_dir: path.into(),
|
||||
tests,
|
||||
judge_type,
|
||||
extra_files,
|
||||
code,
|
||||
})
|
||||
}
|
||||
pub fn load(global_config: &config::Config, path: &str) -> Result<JudgeConfig> {
|
||||
let dir = Path::new(&path);
|
||||
|
||||
let config = dir.join("config.yml");
|
||||
let config = match config.as_path().to_str() {
|
||||
Some(value) => value,
|
||||
None => return Err(Error::PathJoinError),
|
||||
};
|
||||
let config = match fs::read_to_string(&config) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
path.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
JudgeConfig::load_yaml(global_config, &config, &path)
|
||||
}
|
||||
}
|
||||
43
src/main.rs
43
src/main.rs
@ -1,44 +1,3 @@
|
||||
use std::env;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod judger;
|
||||
mod process;
|
||||
mod result;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let config = if args.len() > 1 {
|
||||
config::Config::load_from_file(&args[1])
|
||||
} else {
|
||||
config::Config::default()
|
||||
};
|
||||
let config = match config {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let language = config.language_config_from_name("python");
|
||||
println!("{}", language.unwrap());
|
||||
|
||||
let judge_config = match judger::JudgeConfig::load(&config, "example/a_plus_b") {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
println!("{}", judge_config.code.language);
|
||||
|
||||
// TODO: 处理 SE,处理交互问题
|
||||
match process::run(&judge_config) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
// TODO: SE
|
||||
eprintln!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
println!("Hello World!");
|
||||
}
|
||||
|
||||
323
src/process.rs
323
src/process.rs
@ -1,323 +0,0 @@
|
||||
extern crate libc;
|
||||
|
||||
use super::error::{errno_str, Error, Result};
|
||||
use super::judger::{JudgeConfig, TestCase};
|
||||
use super::result::{ExitStatus, TestCaseResult};
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use libc::{c_long, suseconds_t, time_t};
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::time::SystemTime;
|
||||
use tempfile::tempdir;
|
||||
|
||||
pub struct ExecArgs {
|
||||
pub pathname: *const libc::c_char,
|
||||
pub argv: *const *const libc::c_char,
|
||||
pub envp: *const *const libc::c_char,
|
||||
}
|
||||
|
||||
impl ExecArgs {
|
||||
fn build(cmd: &String, judge_config: &JudgeConfig) -> Result<ExecArgs> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
let _ = handlebars.register_template_string("build", cmd);
|
||||
let mut data = BTreeMap::new();
|
||||
|
||||
data.insert("filename", judge_config.code.file.clone());
|
||||
|
||||
let formatted = match handlebars.render("build", &data) {
|
||||
Ok(val) => val,
|
||||
Err(err) => return Err(Error::TemplateRenderError(err)),
|
||||
};
|
||||
let splited = formatted.split_whitespace();
|
||||
let splited: Vec<&str> = splited.collect();
|
||||
|
||||
if splited.len() < 1 {
|
||||
return Err(Error::LanguageConfigError(formatted));
|
||||
}
|
||||
let pathname = splited[0].clone();
|
||||
let pathname_str = match CString::new(pathname) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let pathname = pathname_str.as_ptr();
|
||||
|
||||
let mut argv_vec: Vec<*const libc::c_char> = vec![];
|
||||
for item in splited.iter() {
|
||||
let cstr = match CString::new(item.clone()) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let cptr = cstr.as_ptr();
|
||||
// 需要使用 mem::forget 来标记
|
||||
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
||||
mem::forget(cstr);
|
||||
argv_vec.push(cptr);
|
||||
}
|
||||
// argv 与 envp 的参数需要使用 NULL 来标记结束
|
||||
argv_vec.push(ptr::null());
|
||||
let argv: *const *const libc::c_char = argv_vec.as_ptr() as *const *const libc::c_char;
|
||||
|
||||
// env 环境变量传递当前进程环境变量
|
||||
let mut envp_vec: Vec<*const libc::c_char> = vec![];
|
||||
for (key, value) in env::vars_os() {
|
||||
let mut key = match key.to_str() {
|
||||
Some(val) => val.to_string(),
|
||||
None => return Err(Error::OsStringToStringError(key)),
|
||||
};
|
||||
let value = match value.to_str() {
|
||||
Some(val) => val.to_string(),
|
||||
None => return Err(Error::OsStringToStringError(value)),
|
||||
};
|
||||
key.push_str("=");
|
||||
key.push_str(&value);
|
||||
let cstr = match CString::new(key) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let cptr = cstr.as_ptr();
|
||||
// 需要使用 mem::forget 来标记
|
||||
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
||||
mem::forget(cstr);
|
||||
envp_vec.push(cptr);
|
||||
}
|
||||
envp_vec.push(ptr::null());
|
||||
let envp = envp_vec.as_ptr() as *const *const libc::c_char;
|
||||
|
||||
mem::forget(pathname_str);
|
||||
mem::forget(argv_vec);
|
||||
mem::forget(envp_vec);
|
||||
|
||||
Ok(ExecArgs {
|
||||
pathname,
|
||||
argv,
|
||||
envp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExecArgs {
|
||||
fn drop(&mut self) {
|
||||
// TODO: 将不安全的指针类型转换回内置类型,以便由 Rust 自动回收资源
|
||||
// 如果能保证资源能够正确 drop,不会出现泄漏,则 ExecArgs 相关的函数不必标注 unsafe
|
||||
println!("Dropping!");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(judge_config: &JudgeConfig) -> Result<()> {
|
||||
let pwd = match tempdir() {
|
||||
Ok(val) => val,
|
||||
Err(err) => return Err(Error::CreateTempDirError(err)),
|
||||
};
|
||||
let from_dir = Path::new(&judge_config.config_dir);
|
||||
let to_dir = pwd.path();
|
||||
|
||||
let mut copy_files: Vec<String> = vec![];
|
||||
copy_files.push(judge_config.code.file.clone());
|
||||
copy_files.extend_from_slice(&judge_config.extra_files);
|
||||
for test_case in &judge_config.tests {
|
||||
copy_files.push(test_case.input_file.clone());
|
||||
}
|
||||
// copy file
|
||||
for file in ©_files {
|
||||
let from_file = from_dir.join(file);
|
||||
let to_file = to_dir.join(file);
|
||||
match fs::copy(from_file, to_file) {
|
||||
Ok(_) => {}
|
||||
Err(err) => return Err(Error::CopyFileError(err)),
|
||||
};
|
||||
}
|
||||
// TODO: 处理 CE 与 RE(编译期间程序运行出错为 CE,运行期间出错为 RE)
|
||||
let compile_status = compile(judge_config, to_dir)?;
|
||||
if !compile_status.exited {
|
||||
// TODO: CE
|
||||
}
|
||||
|
||||
for test_case in &judge_config.tests {
|
||||
let exit_status = run_one(judge_config, test_case, to_dir)?;
|
||||
let output_file_path = to_dir.join(&test_case.output_file);
|
||||
let answer_file_path = from_dir.join(&test_case.answer_file);
|
||||
if !exit_status.exited {
|
||||
// TODO: RE
|
||||
}
|
||||
let res = TestCaseResult::standard(&output_file_path, &answer_file_path, exit_status);
|
||||
println!("{:?}", res);
|
||||
match fs::remove_file(output_file_path) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
return Err(Error::RemoveFileError(
|
||||
test_case.output_file.clone(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
println!("{:?}", pwd);
|
||||
// match pwd.close() {
|
||||
// Ok(_) => {}
|
||||
// Err(err) => return Err(Error::CloseTempDirError(err)),
|
||||
// };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait(pid: i32) -> Result<ExitStatus> {
|
||||
let start = SystemTime::now();
|
||||
let mut status = 0;
|
||||
let mut rusage = libc::rusage {
|
||||
ru_utime: libc::timeval {
|
||||
tv_sec: 0 as time_t,
|
||||
tv_usec: 0 as suseconds_t,
|
||||
},
|
||||
ru_stime: libc::timeval {
|
||||
tv_sec: 0 as time_t,
|
||||
tv_usec: 0 as suseconds_t,
|
||||
},
|
||||
ru_maxrss: 0 as c_long,
|
||||
ru_ixrss: 0 as c_long,
|
||||
ru_idrss: 0 as c_long,
|
||||
ru_isrss: 0 as c_long,
|
||||
ru_minflt: 0 as c_long,
|
||||
ru_majflt: 0 as c_long,
|
||||
ru_nswap: 0 as c_long,
|
||||
ru_inblock: 0 as c_long,
|
||||
ru_oublock: 0 as c_long,
|
||||
ru_msgsnd: 0 as c_long,
|
||||
ru_msgrcv: 0 as c_long,
|
||||
ru_nsignals: 0 as c_long,
|
||||
ru_nvcsw: 0 as c_long,
|
||||
ru_nivcsw: 0 as c_long,
|
||||
};
|
||||
unsafe {
|
||||
let val = libc::wait4(pid, &mut status, 0, &mut rusage);
|
||||
if val < 0 {
|
||||
return Err(Error::WaitError(io::Error::last_os_error().raw_os_error()));
|
||||
}
|
||||
}
|
||||
let time_used = rusage.ru_utime.tv_sec * 1000
|
||||
+ rusage.ru_utime.tv_usec / 1000
|
||||
+ rusage.ru_stime.tv_sec * 1000
|
||||
+ rusage.ru_stime.tv_usec / 1000;
|
||||
let memory_used = rusage.ru_maxrss;
|
||||
let real_time_used = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(err) => return Err(Error::SystemTimeError(err)),
|
||||
};
|
||||
let signal = unsafe {
|
||||
if libc::WIFSIGNALED(status) {
|
||||
libc::WTERMSIG(status)
|
||||
} else if libc::WIFSTOPPED(status) {
|
||||
libc::WSTOPSIG(status)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
let exited = unsafe { libc::WIFEXITED(status) };
|
||||
if exited {
|
||||
status = unsafe { libc::WEXITSTATUS(status) };
|
||||
}
|
||||
let coredump = unsafe { libc::WCOREDUMP(status) };
|
||||
Ok(ExitStatus {
|
||||
rusage,
|
||||
status,
|
||||
signal,
|
||||
exited,
|
||||
coredump,
|
||||
time_used,
|
||||
real_time_used,
|
||||
memory_used,
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe fn dup(filename: &str, to: libc::c_int, flag: libc::c_int, mode: libc::c_int) {
|
||||
let filename_str = CString::new(filename).unwrap();
|
||||
let filename = filename_str.as_ptr();
|
||||
let fd = libc::open(filename, flag, mode);
|
||||
if fd < 0 {
|
||||
let err = io::Error::last_os_error().raw_os_error();
|
||||
eprintln!("open filure!");
|
||||
panic!(errno_str(err));
|
||||
}
|
||||
if libc::dup2(fd, to) < 0 {
|
||||
let err = io::Error::last_os_error().raw_os_error();
|
||||
eprintln!("dup2 filure!");
|
||||
panic!(errno_str(err));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_one(
|
||||
judge_config: &JudgeConfig,
|
||||
test_case: &TestCase,
|
||||
workdir: &Path,
|
||||
) -> Result<ExitStatus> {
|
||||
let pid;
|
||||
|
||||
// 这个操作就需要 1-2ms 左右,此处预先完成,不占用子进程运行时间
|
||||
// 因此,父进程需要 drop 这个结构,需要保证没有内存泄漏
|
||||
let exec_args = ExecArgs::build(
|
||||
&judge_config.code.language.run_command.clone(),
|
||||
&judge_config,
|
||||
)?;
|
||||
unsafe {
|
||||
pid = libc::fork();
|
||||
}
|
||||
if pid == 0 {
|
||||
// 子进程
|
||||
// 此处如果出现 Error,则直接程序崩溃,父进程可以收集异常的信息
|
||||
|
||||
// 修改工作目录
|
||||
env::set_current_dir(workdir).unwrap();
|
||||
unsafe {
|
||||
dup(
|
||||
&test_case.input_file,
|
||||
libc::STDIN_FILENO,
|
||||
libc::O_RDONLY,
|
||||
0o644,
|
||||
);
|
||||
dup(
|
||||
&test_case.output_file,
|
||||
libc::STDOUT_FILENO,
|
||||
libc::O_CREAT | libc::O_RDWR,
|
||||
0o644,
|
||||
);
|
||||
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
||||
}
|
||||
// 理论上,不会走到这里,在上一句 exec 后,程序就已经被替换为待执行程序了
|
||||
// 所以, How dare you!
|
||||
panic!("How dare you!");
|
||||
} else if pid > 0 {
|
||||
// 父进程
|
||||
return Ok(wait(pid)?);
|
||||
} else {
|
||||
// 异常
|
||||
return Err(Error::ForkError(io::Error::last_os_error().raw_os_error()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(judge_config: &JudgeConfig, workdir: &Path) -> Result<ExitStatus> {
|
||||
let pid;
|
||||
unsafe {
|
||||
pid = libc::fork();
|
||||
}
|
||||
if pid == 0 {
|
||||
env::set_current_dir(workdir).unwrap();
|
||||
let exec_args = ExecArgs::build(
|
||||
&judge_config.code.language.compile_command.clone(),
|
||||
&judge_config,
|
||||
)
|
||||
.unwrap();
|
||||
unsafe {
|
||||
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
||||
}
|
||||
panic!("How dare you!");
|
||||
} else if pid > 0 {
|
||||
return Ok(wait(pid)?);
|
||||
} else {
|
||||
return Err(Error::ForkError(io::Error::last_os_error().raw_os_error()));
|
||||
}
|
||||
}
|
||||
142
src/result.rs
142
src/result.rs
@ -1,142 +0,0 @@
|
||||
extern crate libc;
|
||||
|
||||
use super::error::{Error, Result};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct ExitStatus {
|
||||
pub rusage: libc::rusage,
|
||||
pub status: i32,
|
||||
pub signal: i32,
|
||||
pub exited: bool,
|
||||
pub coredump: bool,
|
||||
pub time_used: i64,
|
||||
pub real_time_used: u128,
|
||||
pub memory_used: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TestCaseResult {
|
||||
Accepted,
|
||||
CompileError(String),
|
||||
WrongAnswer,
|
||||
RuntimeError(String),
|
||||
SystemError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for TestCaseResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCaseResult {
|
||||
pub fn standard<P: AsRef<Path>>(
|
||||
output_file: P,
|
||||
answer_file: P,
|
||||
exit_status: ExitStatus,
|
||||
) -> Result<TestCaseResult> {
|
||||
println!("\n----------------------------------\n");
|
||||
println!("time used:\t{}", exit_status.time_used);
|
||||
println!("real time used:\t{}", exit_status.real_time_used);
|
||||
println!("memory used:\t{}", exit_status.rusage.ru_maxrss);
|
||||
println!("status:\t{}", exit_status.status);
|
||||
println!("signal:\t{}", exit_status.signal);
|
||||
println!("exited:\t{}", exit_status.exited);
|
||||
|
||||
let output_filename = match output_file.as_ref().to_str() {
|
||||
Some(val) => val,
|
||||
None => "",
|
||||
};
|
||||
let answer_filename = match answer_file.as_ref().to_str() {
|
||||
Some(val) => val,
|
||||
None => "",
|
||||
};
|
||||
|
||||
let output_file = match File::open(output_filename) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::OpenFileError(
|
||||
output_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut output_reader = BufReader::new(output_file);
|
||||
let mut output_buffer = String::new();
|
||||
let mut output_bytes;
|
||||
|
||||
let answer_file = match File::open(answer_filename) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::OpenFileError(
|
||||
answer_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut answer_reader = BufReader::new(answer_file);
|
||||
let mut answer_buffer = String::new();
|
||||
let mut answer_bytes;
|
||||
|
||||
loop {
|
||||
output_bytes = match output_reader.read_line(&mut output_buffer) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
output_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
answer_bytes = match answer_reader.read_line(&mut answer_buffer) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
answer_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
if output_bytes == 0 || answer_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
if output_buffer.trim_end() != answer_buffer.trim_end() {
|
||||
return Ok(TestCaseResult::WrongAnswer);
|
||||
}
|
||||
}
|
||||
// 末尾的空白字符不影响结果
|
||||
while output_bytes != 0 {
|
||||
if output_buffer.trim() != "" {
|
||||
return Ok(TestCaseResult::WrongAnswer);
|
||||
}
|
||||
output_bytes = match output_reader.read_line(&mut output_buffer) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
output_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
while answer_bytes != 0 {
|
||||
if answer_buffer.trim() != "" {
|
||||
return Ok(TestCaseResult::WrongAnswer);
|
||||
}
|
||||
answer_bytes = match answer_reader.read_line(&mut answer_buffer) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return Err(Error::ReadFileError(
|
||||
answer_filename.to_string(),
|
||||
io::Error::last_os_error().raw_os_error(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(TestCaseResult::Accepted)
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
languages:
|
||||
- language: c
|
||||
version: 7.5.0
|
||||
compile_command: /usr/bin/gcc {{filename}} -o a.out
|
||||
run_command: ./a.out
|
||||
- language: python
|
||||
version: 3.6.9
|
||||
compile_command: /usr/bin/python3 -m compileall {{filename}}
|
||||
run_command: /use/bin/python3 {{filename}}
|
||||
@ -1,44 +0,0 @@
|
||||
{
|
||||
"compile": {
|
||||
"time": 120,
|
||||
"memory": 4096,
|
||||
"status": 0,
|
||||
"signal": 0,
|
||||
"exited": true,
|
||||
"output": "main.c: In function ‘main’: \nmain.c:5:5: warning: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Wunused-result] \n scanf(\"%d %d\", &a, &b);"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"time": 0,
|
||||
"memory": 1576,
|
||||
"status": 0,
|
||||
"signal": 0,
|
||||
"exited": true,
|
||||
"result": "Accepted"
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
"memory": 1576,
|
||||
"status": 0,
|
||||
"signal": 0,
|
||||
"exited": true,
|
||||
"result": "WrongAnswer"
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
"memory": 1576,
|
||||
"status": 0,
|
||||
"signal": 0,
|
||||
"exited": true,
|
||||
"result": "Accepted"
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
"memory": 1576,
|
||||
"status": 139,
|
||||
"signal": 11,
|
||||
"exited": false,
|
||||
"result": "RuntimeError"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user