diff --git a/Cargo.toml b/Cargo.toml index 43fd66ae..480782ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ exclude = ["docs/*", "examples/*", "*.md", "website/*"] include = ["**/*.rs", "Cargo.toml"] [dependencies] +rlimit = "0.10.1" clap = "4.5" colored = "3.0.0" difference = "2.0.0" diff --git a/src/runner/cmd.rs b/src/runner/cmd.rs index d4452c15..23007f5b 100644 --- a/src/runner/cmd.rs +++ b/src/runner/cmd.rs @@ -6,6 +6,8 @@ use std::fs::File; use std::io::Write; +#[cfg(target_os = "macos")] +use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; @@ -43,6 +45,16 @@ pub fn execute_program( cmd.args(&commands[1..]); } + #[cfg(target_os = "macos")] + { + // On macOS, process_control's memory_limit is a no-op. + // We use setrlimit via a pre_exec hook to set the memory limit. + cmd.pre_exec(move || { + rlimit::setrlimit(rlimit::Resource::AS, memory_limit, memory_limit) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) + }); + } + if let Some(file) = &stdin { // set output file, only exists let input = File::open(file.to_str().unwrap()).unwrap(); @@ -78,6 +90,8 @@ pub fn execute_program( #[cfg(target_os = "macos")] let response = child_output .controlled_with_output() + // memory_limit is a no-op on macOS + .memory_limit(memory_limit as usize) .time_limit(Duration::from_millis(timeout as u64)) .terminate_for_timeout() .wait(); @@ -109,7 +123,10 @@ pub fn execute_program( } else { #[cfg(unix)] match output.status.signal() { - Some(6) => res_status = CPStatus::MLE, // SIGABRT: 6 + // SIGABRT: 6 + Some(6) => res_status = CPStatus::MLE, + // SIGKILL: 9 + Some(9) if cfg!(target_os = "macos") => res_status = CPStatus::MLE, _ => res_status = CPStatus::RTE, } diff --git a/tests/cli/stress_subcommand/c_mle.rs b/tests/cli/stress_subcommand/c_mle.rs new file mode 100644 index 00000000..301821b2 --- /dev/null +++ b/tests/cli/stress_subcommand/c_mle.rs @@ -0,0 +1,68 @@ +/* + * Quick Test: CLI for stress testing in competitive programming + * Copyright (C) 2021-present / Luis Miguel Báez + * License: MIT (See the LICENSE file in the repository root directory) + */ + +#![allow(unused_imports)] + +use std::{error::Error, process::Command}; + +use assert_cmd::assert::OutputAssertExt; +use predicates::prelude::predicate; + +use crate::util::{ + test_command_handler::execute_command_stress_with_timeout, + test_constants::{ + BINARY, FOLDER_STRESS, GEN_FILE_C, MLE_C, TARGET_FILE_C, + }, + test_utilities::create_files_tle, +}; + +use super::codes::{GEN_C_STRESS, TARGET_C_STRESS}; + +#[test] +fn cmd_stress_target_mle_c() -> Result<(), Box> { + let folder = "stress_mle_c"; + create_files_tle( + TARGET_FILE_C, + GEN_FILE_C, + MLE_C, + GEN_C_STRESS, + folder, + )?; + let cases: usize = 3; + + let mut cmd = Command::new(BINARY); + execute_command_stress_with_timeout(&mut cmd, TARGET_FILE_C, GEN_FILE_C, cases, 5000usize, folder); + + cmd.assert() + .failure() + .stdout(predicate::str::contains("[MLE]").count(cases)); + + Ok(()) +} + +#[test] +fn cmd_stress_gen_mle_c() -> Result<(), Box> { + let folder = "stress_mle_c_gen"; + create_files_tle( + TARGET_FILE_C, + GEN_FILE_C, + TARGET_C_STRESS, + MLE_C, + folder, + )?; + let cases: usize = 3; + + let mut cmd = Command::new(BINARY); + execute_command_stress_with_timeout(&mut cmd, TARGET_FILE_C, GEN_FILE_C, cases, 5000usize, folder); + + cmd.assert() + .failure() + .stderr(predicate::str::contains( + "Error: QTEST_MEMORY_LIMIT_EXCEEDED\nInfo: caused by generator file give memory limit exceeded / label \n").count(1), + ); + + Ok(()) +} diff --git a/tests/cli/stress_subcommand/codes.rs b/tests/cli/stress_subcommand/codes.rs index d0364e50..9661ce7a 100644 --- a/tests/cli/stress_subcommand/codes.rs +++ b/tests/cli/stress_subcommand/codes.rs @@ -64,3 +64,50 @@ A.sort() print(n) print(*A) "#; + +pub const GEN_C_STRESS: &str = r#" +#include +#include +#include + +int random_int(int min, int max) { + return min + rand() % (max - min + 1); +} + +int main() { + srand(time(NULL)); + int n = random_int(100000, 200000); + printf("%d\n", n); + for (int i = 0; i < n; ++i) { + printf("%d ", random_int(1, 1000000000)); + } + printf("\n"); + return 0; +} +"#; + +pub const TARGET_C_STRESS: &str = r#" +#include +#include + +int compare(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +int main() { + int n; + scanf("%d", &n); + int *A = (int *)malloc(n * sizeof(int)); + for (int i = 0; i < n; ++i) { + scanf("%d", &A[i]); + } + qsort(A, n, sizeof(int), compare); + printf("%d\n", n); + for (int i = 0; i < n; ++i) { + printf("%d ", A[i]); + } + printf("\n"); + free(A); + return 0; +} +"#; diff --git a/tests/cli/stress_subcommand/cpp_mle.rs b/tests/cli/stress_subcommand/cpp_mle.rs index 48964894..d606f8db 100644 --- a/tests/cli/stress_subcommand/cpp_mle.rs +++ b/tests/cli/stress_subcommand/cpp_mle.rs @@ -20,19 +20,26 @@ use crate::util::{ use super::codes::{GEN_CPP_STRESS, TARGET_CPP_STRESS}; #[test] -#[cfg(not(target_os = "macos"))] fn cmd_stress_target_mle_cpp() -> Result<(), Box> { + let folder = "stress_mle_cpp"; create_files_tle( TARGET_FILE_CPP, GEN_FILE_CPP, MLE_CPP, GEN_CPP_STRESS, - FOLDER_STRESS, + folder, )?; let cases: usize = 3; let mut cmd = Command::new(BINARY); - execute_command_stress_with_timeout(&mut cmd, TARGET_FILE_CPP, GEN_FILE_CPP, cases, 5000usize); + execute_command_stress_with_timeout( + &mut cmd, + TARGET_FILE_CPP, + GEN_FILE_CPP, + cases, + 5000usize, + folder, + ); cmd.assert() .failure() @@ -42,19 +49,26 @@ fn cmd_stress_target_mle_cpp() -> Result<(), Box> { } #[test] -#[cfg(not(target_os = "macos"))] fn cmd_stress_gen_mle_cpp() -> Result<(), Box> { + let folder = "stress_mle_cpp_gen"; create_files_tle( TARGET_FILE_CPP, GEN_FILE_CPP, TARGET_CPP_STRESS, MLE_CPP, - FOLDER_STRESS, + folder, )?; let cases: usize = 3; let mut cmd = Command::new(BINARY); - execute_command_stress_with_timeout(&mut cmd, TARGET_FILE_CPP, GEN_FILE_CPP, cases, 5000usize); + execute_command_stress_with_timeout( + &mut cmd, + TARGET_FILE_CPP, + GEN_FILE_CPP, + cases, + 5000usize, + folder, + ); cmd.assert() .failure() diff --git a/tests/cli/stress_subcommand/mod.rs b/tests/cli/stress_subcommand/mod.rs index 34fc0255..5c3e2c86 100644 --- a/tests/cli/stress_subcommand/mod.rs +++ b/tests/cli/stress_subcommand/mod.rs @@ -11,3 +11,4 @@ pub mod cpp_mle; pub mod cpp_rte; pub mod python; pub mod python_rte; +mod c_mle; diff --git a/tests/util/test_command_handler.rs b/tests/util/test_command_handler.rs index ce4cc60c..3440fdd6 100644 --- a/tests/util/test_command_handler.rs +++ b/tests/util/test_command_handler.rs @@ -11,7 +11,14 @@ use crate::util::test_constants::{FOLDER, FOLDER_CHECK, FOLDER_CMP, FOLDER_STRES use super::test_constants::FOLDER_OUTPUT; pub fn execute_command_stress(cmd: &mut Command, target_file: &str, gen_file: &str, cases: usize) { - execute_command_stress_with_timeout(cmd, target_file, gen_file, cases, 1000usize); + execute_command_stress_with_timeout( + cmd, + target_file, + gen_file, + cases, + 1000usize, + FOLDER_STRESS, + ); } pub fn execute_command_stress_with_timeout( @@ -20,12 +27,13 @@ pub fn execute_command_stress_with_timeout( gen_file: &str, cases: usize, timeout: usize, + folder: &str, ) { cmd.arg("stress") .arg("--target-file") - .arg(format!("{}/{}/{}", FOLDER, FOLDER_STRESS, target_file)) + .arg(format!("{}/{}/{}", FOLDER, folder, target_file)) .arg("--gen-file") - .arg(format!("{}/{}/{}", FOLDER, FOLDER_STRESS, gen_file)) + .arg(format!("{}/{}/{}", FOLDER, folder, gen_file)) .arg(format!("--timeout={}", timeout)) .arg(format!("--test-cases={}", cases)); } diff --git a/tests/util/test_constants.rs b/tests/util/test_constants.rs index 1d5ab42a..32409240 100644 --- a/tests/util/test_constants.rs +++ b/tests/util/test_constants.rs @@ -90,6 +90,7 @@ int main() { pub const MLE_C: &str = r#" #include +#include int * a[100000]; int main() { int n = 1000000000;