diff --git a/src/error.rs b/src/error.rs index 3c39faf..990e9c6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,8 @@ pub enum Error { WaitError(Option), SystemTimeError(time::SystemTimeError), RemoveFileError(String, Option), + + OpenFileError(String, Option), } pub type Result = result::Result; @@ -56,6 +58,10 @@ impl fmt::Display for Error { 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) } diff --git a/src/main.rs b/src/main.rs index 08f5420..3694d55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ fn main() { }; println!("{}", judge_config.code.language); + // TODO: 处理 SE,处理交互问题 match process::run(&judge_config) { Ok(_) => {} Err(err) => { diff --git a/src/process.rs b/src/process.rs index fe7428e..294c221 100644 --- a/src/process.rs +++ b/src/process.rs @@ -2,6 +2,8 @@ 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; @@ -131,11 +133,14 @@ pub fn run(judge_config: &JudgeConfig) -> Result<()> { Err(err) => return Err(Error::CopyFileError(err)), }; } + // TODO: 处理 CE 与 RE(编译期间程序运行出错为 CE,运行期间出错为 RE) let _ = compile(judge_config, to_dir)?; for test_case in &judge_config.tests { - println!("\n----------------------------------\n"); - let _ = run_one(judge_config, test_case, to_dir)?; + 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); + let res = TestCaseResult::standard(&output_file_path, &answer_file_path, exit_status); + println!("{:?}", res); match fs::remove_file(output_file_path) { Ok(_) => {} Err(_) => { @@ -153,14 +158,6 @@ pub fn run(judge_config: &JudgeConfig) -> Result<()> { Ok(()) } -pub struct ExitStatus { - pub rusage: libc::rusage, - pub status: i32, - pub time_used: i64, - pub real_time_used: u128, - pub memory_used: i64, -} - pub fn wait(pid: i32) -> Result { let start = SystemTime::now(); let mut status = 0; @@ -203,9 +200,6 @@ pub fn wait(pid: i32) -> Result { Ok(elapsed) => elapsed.as_millis(), Err(err) => return Err(Error::SystemTimeError(err)), }; - println!("time used:\t{}", time_used); - println!("real time used:\t{}", real_time_used); - println!("memory used:\t{}", rusage.ru_maxrss); Ok(ExitStatus { rusage, status, @@ -231,8 +225,11 @@ pub unsafe fn dup(filename: &str, to: libc::c_int, flag: libc::c_int, mode: libc } } -pub fn run_one(judge_config: &JudgeConfig, test_case: &TestCase, workdir: &Path) -> Result<()> { - println!("running test case: {}", test_case.index); +pub fn run_one( + judge_config: &JudgeConfig, + test_case: &TestCase, + workdir: &Path, +) -> Result { let pid; // 这个操作就需要 1-2ms 左右,此处预先完成,不占用子进程运行时间 @@ -255,13 +252,13 @@ pub fn run_one(judge_config: &JudgeConfig, test_case: &TestCase, workdir: &Path) &test_case.input_file, libc::STDIN_FILENO, libc::O_RDONLY, - 0644, + 0o644, ); dup( &test_case.output_file, libc::STDOUT_FILENO, libc::O_CREAT | libc::O_RDWR, - 0644, + 0o644, ); libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp); } @@ -270,12 +267,12 @@ pub fn run_one(judge_config: &JudgeConfig, test_case: &TestCase, workdir: &Path) panic!("How dare you!"); } else if pid > 0 { // 父进程 - let _ = wait(pid)?; + let exit_status = wait(pid)?; + return Ok(exit_status); } else { // 异常 return Err(Error::ForkError(io::Error::last_os_error().raw_os_error())); } - Ok(()) } pub fn compile(judge_config: &JudgeConfig, workdir: &Path) -> Result<()> { diff --git a/src/result.rs b/src/result.rs index 2bbd20e..2aafeb6 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,11 +1,23 @@ -use super::error::Result; +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; #[derive(Debug, Copy, Clone)] pub struct ResourceUsed { - pub time_used: u32, - pub memory_used: u32, + pub time_used: i64, + pub memory_used: i64, +} + +pub struct ExitStatus { + pub rusage: libc::rusage, + pub status: i32, + pub time_used: i64, + pub real_time_used: u128, + pub memory_used: i64, } #[derive(Debug, Clone)] @@ -24,12 +36,111 @@ impl fmt::Display for TestCaseResult { } impl TestCaseResult { - pub fn standard>(output_file: P, answer_file: P) -> Result { - let result = TestCaseResult::Accepted(ResourceUsed { - time_used: 0, - memory_used: 0, - }); - // TODO: 对比答案文件,返回结果 - Ok(result) + pub fn standard>( + output_file: P, + answer_file: P, + exit_status: ExitStatus, + ) -> Result { + 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); + let used = ResourceUsed { + time_used: exit_status.time_used, + memory_used: exit_status.rusage.ru_maxrss, + }; + + 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(used)); + } + } + // 末尾的空白字符不影响结果 + while output_bytes != 0 { + if output_buffer.trim() != "" { + return Ok(TestCaseResult::WrongAnswer(used)); + } + 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(used)); + } + 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(used)) } }