From 4f8a2bcb5f42196b1793d5e3205d11479c3f502e Mon Sep 17 00:00:00 2001 From: Patrick Thompson Date: Tue, 28 Apr 2026 23:16:14 +0930 Subject: [PATCH 1/4] feat: Pass input to custom checker --- README.md | 2 +- crates/cli/src/problem/test.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 019f3f7..c3f674b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Problems are stored in a `problems` folder. This can be changed in the `settings Within each difficulty folder, there are the individual problems. Each of these folders will contain a `problem.md` which is the problem statement. There will be a `tests` folder for test cases and a `solutions` folder for reference solutions. -If a problem folder contains a `checker.py` next to `problem.md`, `aucpl problem test` will use it as a custom checker. The file must define `check(process_output, judge_output)` and return a boolean. +If a problem folder contains a `checker.py` next to `problem.md`, `aucpl problem test` will use it as a custom checker. The file must define `check(process_output, judge_output, judge_input)` and return a boolean. Lastly, there is a `problem-mappings.json` file that maps the problem names to its stored location. This is so that in the CLI, you do not have to specify things like the rating or whether it's a new or archived problem. You can also use `aucpl sync` to generate or update the mappings. diff --git a/crates/cli/src/problem/test.rs b/crates/cli/src/problem/test.rs index f9e8946..e739850 100644 --- a/crates/cli/src/problem/test.rs +++ b/crates/cli/src/problem/test.rs @@ -19,6 +19,7 @@ import sys checker_path = sys.argv[1] process_output = sys.argv[2] judge_output = sys.argv[3] +judge_input = sys.argv[4] spec = importlib.util.spec_from_file_location("aucpl_checker", checker_path) if spec is None or spec.loader is None: @@ -32,7 +33,7 @@ if not hasattr(module, "check"): print("checker.py must define a `check` function", file=sys.stderr) sys.exit(2) -result = module.check(process_output, judge_output) +result = module.check(process_output, judge_output, judge_input) print("true" if bool(result) else "false") "#; @@ -42,8 +43,10 @@ fn run_custom_checker( checker_path: &Path, process_output: &str, judge_output: &[u8], + judge_input: &[u8], ) -> Result { let judge_output = String::from_utf8_lossy(judge_output).into_owned(); + let judge_input = String::from_utf8_lossy(judge_input).into_owned(); let python_cmd = get_python_executable(settings); let checker_run = RunCommand::from_command( @@ -56,6 +59,7 @@ fn run_custom_checker( "@script_file".to_string(), process_output.to_string(), judge_output, + judge_input, ], ) .context("Failed to prepare checker command")?; @@ -119,6 +123,10 @@ pub fn test( .context("Failed to strip suffix of test file")? )); + let mut input_file = File::open(&input_file_path)?; + let mut input_bytes: Vec = Vec::new(); + input_file.read_to_end(&mut input_bytes)?; + let result = run_command.get_result(Some(&input_file_path))?; let mut output_file = File::open(output_file_path)?; @@ -130,7 +138,7 @@ pub fn test( output_file.read_to_end(expected)?; let passed = if use_custom_checker { - run_custom_checker(settings, &checker_path, &out_str, expected)? + run_custom_checker(settings, &checker_path, &out_str, expected, &input_bytes)? } else { expected == out_str.as_bytes() }; From 9da384df719442ecf8d60653812e512b92af4700 Mon Sep 17 00:00:00 2001 From: Patrick Thompson Date: Wed, 29 Apr 2026 00:18:53 +0930 Subject: [PATCH 2/4] feat: Pass judge input to custom checkers --- crates/cli/src/problem/test.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/cli/src/problem/test.rs b/crates/cli/src/problem/test.rs index e739850..b53a077 100644 --- a/crates/cli/src/problem/test.rs +++ b/crates/cli/src/problem/test.rs @@ -19,7 +19,8 @@ import sys checker_path = sys.argv[1] process_output = sys.argv[2] judge_output = sys.argv[3] -judge_input = sys.argv[4] + +judge_input = sys.stdin.read() spec = importlib.util.spec_from_file_location("aucpl_checker", checker_path) if spec is None or spec.loader is None: @@ -33,7 +34,7 @@ if not hasattr(module, "check"): print("checker.py must define a `check` function", file=sys.stderr) sys.exit(2) -result = module.check(process_output, judge_output, judge_input) +result = module.check(process_output, judge_output, judge_input=judge_input) print("true" if bool(result) else "false") "#; @@ -43,10 +44,9 @@ fn run_custom_checker( checker_path: &Path, process_output: &str, judge_output: &[u8], - judge_input: &[u8], + input_file_path: &PathBuf, ) -> Result { let judge_output = String::from_utf8_lossy(judge_output).into_owned(); - let judge_input = String::from_utf8_lossy(judge_input).into_owned(); let python_cmd = get_python_executable(settings); let checker_run = RunCommand::from_command( @@ -59,13 +59,11 @@ fn run_custom_checker( "@script_file".to_string(), process_output.to_string(), judge_output, - judge_input, ], ) .context("Failed to prepare checker command")?; - let checker_result = checker_run - .get_result(None) + .get_result(Some(input_file_path)) .context("Failed to run checker.py")? .output; @@ -123,10 +121,6 @@ pub fn test( .context("Failed to strip suffix of test file")? )); - let mut input_file = File::open(&input_file_path)?; - let mut input_bytes: Vec = Vec::new(); - input_file.read_to_end(&mut input_bytes)?; - let result = run_command.get_result(Some(&input_file_path))?; let mut output_file = File::open(output_file_path)?; @@ -138,7 +132,7 @@ pub fn test( output_file.read_to_end(expected)?; let passed = if use_custom_checker { - run_custom_checker(settings, &checker_path, &out_str, expected, &input_bytes)? + run_custom_checker(settings, &checker_path, &out_str, expected, &input_file_path)? } else { expected == out_str.as_bytes() }; From 5af9676d27974691c061e0439836188d5f963f9e Mon Sep 17 00:00:00 2001 From: Patrick Thompson Date: Wed, 29 Apr 2026 11:09:52 +0930 Subject: [PATCH 3/4] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3f674b..a7bdc9c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Problems are stored in a `problems` folder. This can be changed in the `settings Within each difficulty folder, there are the individual problems. Each of these folders will contain a `problem.md` which is the problem statement. There will be a `tests` folder for test cases and a `solutions` folder for reference solutions. -If a problem folder contains a `checker.py` next to `problem.md`, `aucpl problem test` will use it as a custom checker. The file must define `check(process_output, judge_output, judge_input)` and return a boolean. +If a problem folder contains a `checker.py` next to `problem.md`, `aucpl problem test` will use it as a custom checker. The file must define `check(process_output, judge_output, **kwargs)` and return a boolean. The `judge_input` value is provided in `kwargs`. Lastly, there is a `problem-mappings.json` file that maps the problem names to its stored location. This is so that in the CLI, you do not have to specify things like the rating or whether it's a new or archived problem. You can also use `aucpl sync` to generate or update the mappings. From 96771a8176b685b06c163b69fce1fb4677937a67 Mon Sep 17 00:00:00 2001 From: Patrick Thompson Date: Wed, 29 Apr 2026 11:11:01 +0930 Subject: [PATCH 4/4] chore: run fmt --- crates/cli/src/problem/test.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/problem/test.rs b/crates/cli/src/problem/test.rs index b53a077..52b51dc 100644 --- a/crates/cli/src/problem/test.rs +++ b/crates/cli/src/problem/test.rs @@ -132,7 +132,13 @@ pub fn test( output_file.read_to_end(expected)?; let passed = if use_custom_checker { - run_custom_checker(settings, &checker_path, &out_str, expected, &input_file_path)? + run_custom_checker( + settings, + &checker_path, + &out_str, + expected, + &input_file_path, + )? } else { expected == out_str.as_bytes() };