From 39c850b4f4d4cea810d6a084938e474c98416d82 Mon Sep 17 00:00:00 2001 From: Danil Platonov Date: Sat, 30 May 2026 11:45:57 -0700 Subject: [PATCH] Test that SIGINT interrupts non-terminating goals on unix. Cleanup: use tokio::test instead of special test helpers Added pty_exec.py helper script to run binaries with a pseudoterminal attached (bug won't trigger otherwise). Tested CI to fail on all unixes (ubuntu+macos) with older rustyline, and pass with new one. --- .gitignore | 2 ++ tests-pl/issue-interrupt-nontermination.pl | 38 ++++++++++++++++++++++ tests-pl/pty_exec.py | 14 ++++++++ tests/scryer/helper.rs | 35 -------------------- tests/scryer/issues.rs | 20 +++++++++--- 5 files changed, 69 insertions(+), 40 deletions(-) create mode 100644 tests-pl/issue-interrupt-nontermination.pl create mode 100755 tests-pl/pty_exec.py diff --git a/.gitignore b/.gitignore index c2e60e69..359be019 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ target/ .direnv/ +__pycache__ +*.pyc \ No newline at end of file diff --git a/tests-pl/issue-interrupt-nontermination.pl b/tests-pl/issue-interrupt-nontermination.pl new file mode 100644 index 00000000..75b9b7db --- /dev/null +++ b/tests-pl/issue-interrupt-nontermination.pl @@ -0,0 +1,38 @@ +% Server +:- use_module(library(process)). +:- use_module(library(charsio)). +:- use_module(library(lists)). +:- use_module(library(time)). + +prolog_path(Prolog) :- + read(Body), + term_variables(Body, [Prolog]), + Body. + +main :- + prolog_path(Prolog), + CMD = "\ +use_module(library(os)), \ +pid(PID), \ +write(PID), nl, \ +asserta((f :- f)), \ +catch(f, Err, (write(Err),nl)), \ +write(done), nl, \ +halt.", + process_create("tests-pl/pty_exec.py", [Prolog, "-g", CMD], [stdout(pipe(O))]), + get_line_to_chars(O, PID0, ""), + append(PID, "\r\n", PID0), + process_create("kill", ["-s", "INT", PID], []), + sleep(5), + % second kill should fail because process should exit after one kill + process_create("kill", ["-s", "INT", PID], [stderr(null), process(PK)]), + process_wait(PK, Status), + status_report(Status, PID), nl. + +status_report(exit(1), _PID) :- write(ok). +status_report(exit(0), PID) :- + write(not_dead), + % kill by force to exit cleanly + process_create("kill", ["-9", PID], []). + +:- initialization(main). \ No newline at end of file diff --git a/tests-pl/pty_exec.py b/tests-pl/pty_exec.py new file mode 100755 index 00000000..336f99f3 --- /dev/null +++ b/tests-pl/pty_exec.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# Pseudoterminal wrapper script + +import pty +from sys import argv + +def main(): + if len(argv) < 2: + print(f"Usage: {argv[0]} [command]") + return + pty.spawn(argv[1:]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/scryer/helper.rs b/tests/scryer/helper.rs index 91fe9880..4f3168aa 100644 --- a/tests/scryer/helper.rs +++ b/tests/scryer/helper.rs @@ -38,41 +38,6 @@ pub(crate) fn load_module_test(file: &str, expected: T) { expected.assert_eq(wam.test_load_file(file).as_slice()); } -/// Same as `load_module_test` with tokio runtime -#[cfg(not(target_arch = "wasm32"))] -pub(crate) fn load_module_test_with_tokio_runtime(file: &str, expected: T) { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - runtime.block_on(async move { - let mut wam = MachineBuilder::default().build(); - expected.assert_eq(wam.test_load_file(file).as_slice()) - }); -} - -#[cfg(not(target_arch = "wasm32"))] -pub(crate) fn load_module_test_with_tokio_runtime_and_input( - file: &str, - input: impl Into>, - expected: T, -) { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - runtime.block_on(async move { - let mut wam = MachineBuilder::default() - .with_streams( - StreamConfig::in_memory().with_user_input(InputStreamConfig::string(input)), - ) - .build(); - expected.assert_eq(wam.test_load_file(file).as_slice()) - }); -} - pub(crate) fn load_module_test_with_input( file: &str, input: impl Into>, diff --git a/tests/scryer/issues.rs b/tests/scryer/issues.rs index 1cc9a260..95208a4c 100644 --- a/tests/scryer/issues.rs +++ b/tests/scryer/issues.rs @@ -1,7 +1,5 @@ use crate::helper::load_module_test; use crate::helper::load_module_test_with_input; -#[cfg(all(feature = "http", not(target_arch = "wasm32")))] -use crate::helper::load_module_test_with_tokio_runtime_and_input; use serial_test::serial; // issue #831 @@ -163,13 +161,25 @@ fn issue3262_read_from_stdin_no_newline() { load_module_test_with_input("tests-pl/issue3262.pl", "hello.", "hello"); } -#[test] +#[tokio::test(flavor = "multi_thread")] #[cfg(all(feature = "http", not(target_arch = "wasm32")))] #[cfg_attr(miri, ignore = "it takes too long to run")] -fn http_open_hanging() { - load_module_test_with_tokio_runtime_and_input( +async fn http_open_hanging() { + load_module_test_with_input( "tests-pl/issue-http_open-hanging.pl", format!("PROLOG={:?}.", env!("CARGO_BIN_EXE_scryer-prolog")), "received response with status code:200\nreceived response with status code:200\nreceived response with status code:200\nreceived response with status code:200\nreceived response with status code:200\n" ); } + +#[tokio::test(flavor = "multi_thread")] +#[cfg(feature = "repl")] +#[cfg(unix)] +#[cfg_attr(miri, ignore = "it takes too long to run")] +async fn sigint_interrupts_nonterminating_goals() { + load_module_test_with_input( + "tests-pl/issue-interrupt-nontermination.pl", + format!("PROLOG={:?}.", env!("CARGO_BIN_EXE_scryer-prolog")), + "ok\n", + ); +} -- 2.54.0