Merge pull request #3299 from danilp-id/interrupt_test

Test: SIGINT should be able to interrupt non-terminating goals on linux
This commit is contained in:
Mark Thom
2026-06-04 13:11:08 -07:00
committed by GitHub
5 changed files with 69 additions and 40 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ target/
.direnv/ .direnv/
__pycache__
*.pyc

View File

@@ -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).

14
tests-pl/pty_exec.py Executable file
View File

@@ -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()

View File

@@ -38,41 +38,6 @@ pub(crate) fn load_module_test<T: Expectable>(file: &str, expected: T) {
expected.assert_eq(wam.test_load_file(file).as_slice()); 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<T: Expectable>(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<T: Expectable>(
file: &str,
input: impl Into<Cow<'static, 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()
.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<T: Expectable>( pub(crate) fn load_module_test_with_input<T: Expectable>(
file: &str, file: &str,
input: impl Into<Cow<'static, str>>, input: impl Into<Cow<'static, str>>,

View File

@@ -1,7 +1,5 @@
use crate::helper::load_module_test; use crate::helper::load_module_test;
use crate::helper::load_module_test_with_input; 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; use serial_test::serial;
// issue #831 // issue #831
@@ -163,17 +161,29 @@ fn issue3262_read_from_stdin_no_newline() {
load_module_test_with_input("tests-pl/issue3262.pl", "hello.", "hello"); 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(all(feature = "http", not(target_arch = "wasm32")))]
#[cfg_attr(miri, ignore = "it takes too long to run")] #[cfg_attr(miri, ignore = "it takes too long to run")]
fn http_open_hanging() { async fn http_open_hanging() {
load_module_test_with_tokio_runtime_and_input( load_module_test_with_input(
"tests-pl/issue-http_open-hanging.pl", "tests-pl/issue-http_open-hanging.pl",
format!("PROLOG={:?}.", env!("CARGO_BIN_EXE_scryer-prolog")), 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" "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",
);
}
#[test] #[test]
#[cfg_attr(miri, ignore = "it takes too long to run")] #[cfg_attr(miri, ignore = "it takes too long to run")]
#[cfg_attr( #[cfg_attr(