]> Repositorios git - scryer-prolog.git/commitdiff
get process_create working and add tests
authorBennet Bleßmann <[email protected]>
Sat, 26 Jul 2025 16:21:00 +0000 (18:21 +0200)
committerBennet Bleßmann <[email protected]>
Fri, 1 Aug 2025 18:47:22 +0000 (20:47 +0200)
14 files changed:
build/instructions_template.rs
src/arena.rs
src/heap_print.rs
src/lib/lists.pl
src/lib/process.pl
src/machine/dispatch.rs
src/machine/machine_errors.rs
src/machine/machine_state.rs
src/machine/machine_state_impl.rs
src/machine/system_calls.rs
src/macros.rs
tests/scryer/cli/unix/process.md [new file with mode: 0644]
tests/scryer/cli/windows/process.md [new file with mode: 0644]
tests/scryer/main.rs

index 346e68afc84a28db87db0dc99c8efc7b8a414e30..1cf004b11ceb894b1d6e18527f64f99fe9affef5 100644 (file)
@@ -539,6 +539,8 @@ enum SystemClauseType {
     Shell,
     #[strum_discriminants(strum(props(Arity = "8", Name = "$process_create")))]
     ProcessCreate,
+    #[strum_discriminants(strum(props(Arity = "2", Name = "$process_id")))]
+    ProcessId,
     #[strum_discriminants(strum(props(Arity = "3", Name = "$process_wait")))]
     ProcessWait,
     #[strum_discriminants(strum(props(Arity = "1", Name = "$process_kill")))]
@@ -1834,6 +1836,7 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::CallUnsetEnv |
                     &Instruction::CallShell |
                     &Instruction::CallProcessCreate |
+                    &Instruction::CallProcessId |
                     &Instruction::CallProcessWait |
                     &Instruction::CallProcessKill |
                     &Instruction::CallProcessRelease |
@@ -2076,6 +2079,7 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::ExecuteUnsetEnv |
                     &Instruction::ExecuteShell |
                     &Instruction::ExecuteProcessCreate |
+                    &Instruction::ExecuteProcessId |
                     &Instruction::ExecuteProcessWait |
                     &Instruction::ExecuteProcessKill |
                     &Instruction::ExecuteProcessRelease |
index 2b3120704a7b53d8e87cf606d24a5edca2bd1a4e..ee9dda209cd0b741f796e0321d8319d5fdf5b0cb 100644 (file)
@@ -20,6 +20,7 @@ use std::mem;
 use std::mem::ManuallyDrop;
 use std::net::TcpListener;
 use std::ops::{Deref, DerefMut};
+use std::process::Child;
 use std::ptr;
 use std::ptr::addr_of_mut;
 use std::ptr::NonNull;
@@ -75,7 +76,8 @@ pub enum ArenaHeaderTag {
     HttpResponse = 0b1000010,
     PipeWriter = 0b1000011,
     Dropped = 0b1000100,
-    PipeReader = 0b1000101,
+    PipeReader = 0b1001001,
+    ChildProcess = 0b1001010,
 }
 
 #[bitfield]
@@ -391,6 +393,19 @@ impl ArenaAllocated for HttpResponse {
     }
 }
 
+impl ArenaAllocated for Child {
+    type Payload = ManuallyDrop<Self>;
+    #[inline]
+    fn tag() -> ArenaHeaderTag {
+        ArenaHeaderTag::ChildProcess
+    }
+}
+impl AllocateInArena<Child> for Child {
+    fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<Child> {
+        Child::alloc(arena, ManuallyDrop::new(self))
+    }
+}
+
 #[repr(C)]
 #[derive(Debug)]
 pub struct AllocSlab {
@@ -556,6 +571,9 @@ unsafe fn drop_slab_in_place(value: NonNull<AllocSlab>, tag: ArenaHeaderTag) {
         ArenaHeaderTag::PipeWriter => {
             drop_typed_slab_in_place!(PipeWriter, value);
         }
+        ArenaHeaderTag::ChildProcess => {
+            drop_typed_slab_in_place!(Child, value);
+        }
         ArenaHeaderTag::NullStream => {
             unreachable!("NullStream is never arena allocated!");
         }
index 57fef89557435d74ce4a8c3fec80d3b5b757de60..2186c8edd6b5f2ef419b9638be8f06c7617296b5 100644 (file)
@@ -1789,6 +1789,23 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
                    (ArenaHeaderTag::Dropped, _value) => {
                        self.print_impromptu_atom(atom!("$dropped_value"));
                    }
+                   (ArenaHeaderTag::ChildProcess, process) => {
+
+                        let process_atom = atom!("$process");
+
+                        if self.format_struct(max_depth, 1, process_atom) {
+                            let atom = TokenOrRedirect::NumberFocus(max_depth, NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(process.id()))), op);
+
+                            let process_root = self.state_stack.pop().unwrap();
+
+                            self.state_stack.pop();
+                            self.state_stack.pop();
+
+                            self.state_stack.push(atom);
+                            self.state_stack.push(TokenOrRedirect::Open);
+                            self.state_stack.push(process_root);
+                        }
+                   }
                    _ => {
                    }
                );
index 3d1cc6a2f6744da2ad70a9a1275218c1dd80081e..9c972f018964fd3af7301049f5241b056f6764ec 100644 (file)
@@ -7,7 +7,7 @@ List manipulation predicates
                          maplist/3, maplist/4, maplist/5, maplist/6,
                          maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
                          sum_list/2, transpose/2, list_to_set/2, list_max/2,
-                  list_min/2, permutation/2]).
+                  list_min/2, permutation/2, filter/3]).
 
 /*  Author:        Mark Thom, Jan Wielemaker, and Richard O'Keefe
     Copyright (c)  2018-2021, Mark Thom
@@ -538,3 +538,11 @@ perm([], []).
 perm(List, [First|Perm]) :-
     select(First, List, Rest),
     perm(Rest, Perm).
+
+
+%% filter(+Predicate, ?Xs1 ?Xs2).
+%
+% Succeeds if Xs2 is the list of elements X from Xs1 for which call(Pred, X) succeeds.
+%
+filter(_, [], []).
+filter(Pred, [X1|XS1], XS) :- call(Pred, X1) -> filter(Pred, XS1, XS2), XS = [X1|XS2] ; filter(Pred, XS1, XS).
\ No newline at end of file
index 603a3260a15d9e57237d912cd4eb66e58905f19a..d66c2ea45fbb31d29f56b30b1a0276754f45c077 100644 (file)
@@ -1,5 +1,6 @@
 :- module(process, [
     process_create/3, 
+    process_id/2,
     process_release/1, 
     process_wait/2, 
     process_wait/3, 
@@ -8,7 +9,7 @@
 
 :- use_module(library(error)).
 :- use_module(library(iso_ext)).
-:- use_module(library(lists), [append/3, member/2, maplist/2, maplist/3, select/3]).
+:- use_module(library(lists), [member/2, maplist/2, filter/3]).
 
 
 %% process_create(+Exe, +Args:list, +Options).
@@ -20,7 +21,7 @@
 % Options is a list consisting of the following options:
 %
 %  * `cwd(+Path)`           Set the processes working directory to `Path`
-%  * `process(-Pid)`        `Pid` will be assigned the spawned processes process id
+%  * `process(-Process)`    `Process` will be assigned a process handle for the spawned process
 %  * `env(+List)`           Don't inherit environment variables and set the variables defined in `List`
 %  * `environment(+List)`   Inherit environment variables and set/override the variables defined in `List`
 %  * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
@@ -53,7 +54,7 @@ process_create(Exe, Args, Options) :-
             ([stdout], valid_stdio, stdout(std), stdout(Stdout)),
             ([stderr], valid_stdio, stderr(std), stderr(Stderr)),
             ([env, environment], valid_env, environment([]), Env),
-            ([process], valid_process, process(_), process(Pid)),
+            ([process], valid_uninit_process, process(_), process(Process)),
             ([cwd], valid_cwd, cwd("."), cwd(Cwd))
         ],
         Options
@@ -62,21 +63,27 @@ process_create(Exe, Args, Options) :-
     Stdout =.. Stdout1,
     Stderr =.. Stderr1,
     simplify_env(Env, Env1),
-    '$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Pid).
+    '$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
 
+%% process_id(+Process, -Pid).
+%
+process_id(Process, Pid) :-
+    valid_process(Process, process_id/2),
+    write(valid), nl,
+    must_be(var, Pid),
+    write(var), nl, 
+    '$process_id'(Process, Pid).
 
-%% process_wait(+Pid, Status).
+%% process_wait(+Process, Status).
 %
 % See `process_create/3` with `Options = []`
 %
-process_wait(Pid, Status) :- process_wait(Pid, Status, []).
+process_wait(Process, Status) :- process_wait(Process, Status, []).
 
 
-%% process_wait(+Pid, Status, Options).
-%
-% Wait for the child process with `Pid` to exit.
+%% process_wait(+Process, Status, Options).
 %
-% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
+% Wait for the process behind the process handle `Process` to exit.
 %
 % When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
 % When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
@@ -90,43 +97,42 @@ process_wait(Pid, Status) :- process_wait(Pid, Status, []).
 %
 % - timeout(infinite)
 %
-process_wait(Pid, Status, Options) :- 
-    must_be(integer, Pid),
+process_wait(Process, Status, Options) :- 
+    valid_process(Process, process_wait/3),
     must_be_known_options([timeout], [], Options),check_options(
         [
             ([timeout], valid_timeout, timeout(infinite), timeout(Timeout))
         ],
         Options
     ),
-    '$process_wait'(Pid, Exit, Timeout),
+    '$process_wait'(Process, Exit, Timeout),
     Exit = Status.
 
 valid_timeout(timeout(infinite)).
 valid_timeout(timeout(0)).
 
 
-%% process_kill(+Pid).
+%% process_kill(+Process).
 %
-% Kill the child process identified by `Pid`.
+% Kill the process using the process handle `Process`.
 % On Unix this sends SIGKILL.
 %
 % Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
 % 
-process_kill(Pid) :- 
-    must_be(integer, Pid),
-    '$process_kill'(Pid).
+process_kill(Process) :- 
+    valid_process(Process, process_kill/1),
+    '$process_kill'(Process).
 
-%% process_release(+Pid)
+%% process_release(+Process)
 %
-% release child process object of the process identified by `Pid`
+% wait for the process to exit (if not already) and release process handle `Process`
 %
-% It's an error if 
-% * the `Pid` is not associated with a child process created by `process_create/3`,
-% * the child project object has already been released 
+% It's an error if `Process` is not a valid process handle
 %
-process_release(Pid) :- 
-    process_wait(Pid, _),
-    '$process_release'(Pid).
+process_release(Process) :- 
+    valid_process(Process, process_release/1),
+    process_wait(Process, _),
+    '$process_release'(Process).
 
 
 must_be_known_options(_, _,  []).
@@ -142,16 +148,16 @@ must_be_known_options(Valid, Found, [X|XS]) :-
 check_options([], _).
 check_options([X | XS], Options) :- 
     (Kinds, Pred, Default, Choice) = X,
-    findall(P, find_option(Kinds, P, Options), Solutions),
+    filter(process:find_option(Kinds), Options, Solutions),
     (
         Solutions = [] -> Choice = Default;
         Solutions = [Provided] -> call(Pred, Provided), Choice = Provided ;
-        error(evaluation_error(confliction_options), process_create/3)
+        error(evaluation_error(confliction_options, Solutions), process_create/3)
     ),
     check_options(XS, Options).
 
-find_option([Kind|_], Found, Options) :- Found =.. [Kind,_], member(Found, Options).
-find_option([_|Kinds], Found, Options) :- find_option(Kinds, Found, Options).
+find_option([Kind|_], Found) :- Found =.. [Kind,_].
+find_option([_|Kinds], Found) :- find_option(Kinds, Found).
 
 valid_stdio(IO) :- IO =.. [_, Arg], 
     (
@@ -179,7 +185,9 @@ valid_env_([N=V|ES]) :-
     must_be(chars, V),
     valid_env_(ES).
 
-valid_process(process(Pid)) :- must_be(var, Pid).
+valid_uninit_process(process(Process)) :- must_be(var, Process).
+
+valid_process(Process, Context) :- var(Process) -> instantiation_error(Context) ; true.
 
 valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
 
index 00396e239012d1e40057f821a2facc0fcd1ceea5..8a8921e6b1fa07f3baf0335ce34a408e0b8e6501 100644 (file)
@@ -4795,6 +4795,14 @@ impl Machine {
                         try_or_throw!(self.machine_st, self.process_create());
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
+                    &Instruction::CallProcessId => {
+                        try_or_throw!(self.machine_st, self.process_id());
+                        step_or_fail!(self, self.machine_st.p += 1);
+                    }
+                    &Instruction::ExecuteProcessId => {
+                        try_or_throw!(self.machine_st, self.process_id());
+                        step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
+                    }
                     &Instruction::CallProcessWait => {
                         try_or_throw!(self.machine_st, self.process_wait());
                         step_or_fail!(self, self.machine_st.p += 1);
index 084c8a83ee5244ec52fce4062834a1934c441816..c6836709e89dcbd58d51f7de146095f6b5120f60 100644 (file)
@@ -44,6 +44,7 @@ pub(crate) enum ValidType {
     //    PredicateIndicator,
     //    Variable
     TcpListener,
+    Process,
 }
 
 impl ValidType {
@@ -67,6 +68,7 @@ impl ValidType {
             //            ValidType::PredicateIndicator => atom!("predicate_indicator"),
             //            ValidType::Variable => atom!("variable")
             ValidType::TcpListener => atom!("tcp_listener"),
+            ValidType::Process => atom!("process"),
         }
     }
 }
index 6d583b00d3ace5629fc803697b1e1e9e5ea012e7..7663c95269070715c660b517e8635f8c5e771157 100644 (file)
@@ -20,11 +20,9 @@ use crate::parser::dashu::Integer;
 
 use indexmap::IndexMap;
 
-use std::collections::BTreeMap;
 use std::convert::TryFrom;
 use std::fmt;
 use std::ops::{Index, IndexMut, Range};
-use std::process::Child;
 use std::sync::Arc;
 
 pub(crate) type Registers = [HeapCellValue; MAX_ARITY + 1];
@@ -99,7 +97,6 @@ pub struct MachineState {
     pub(crate) unify_fn: fn(&mut MachineState),
     pub(crate) bind_fn: fn(&mut MachineState, Ref, HeapCellValue),
     pub(crate) run_cleaners_fn: fn(&mut Machine) -> bool,
-    pub(crate) child_processes: BTreeMap<u32, Child>,
 }
 
 impl fmt::Debug for MachineState {
index e8b9d644bc8e2e85b24872bf1701615f89508cbf..3f14253d03e2dde95b548b76da162885df5037fc 100644 (file)
@@ -19,7 +19,6 @@ use crate::types::*;
 use indexmap::IndexSet;
 
 use std::cmp::Ordering;
-use std::collections::BTreeMap;
 use std::convert::TryFrom;
 
 impl MachineState {
@@ -68,7 +67,6 @@ impl MachineState {
             unify_fn: MachineState::unify,
             bind_fn: MachineState::bind,
             run_cleaners_fn: |_| false,
-            child_processes: BTreeMap::new(),
         }
     }
 
index 214c6d3ac09144d0080b22208735eb910124030c..0fc6e35dd71b242beb3cd02e4f90653d600fe74b 100644 (file)
@@ -56,6 +56,7 @@ use std::net::{SocketAddr, ToSocketAddrs};
 use std::net::{TcpListener, TcpStream};
 use std::num::NonZeroU32;
 use std::process;
+use std::process::Child;
 use std::process::Stdio;
 #[cfg(feature = "http")]
 use std::str::FromStr;
@@ -8496,16 +8497,13 @@ impl Machine {
 
         match command.spawn() {
             Ok(child) => {
-                let pid = child.id();
-
-                dbg!(pid);
-
-                self.machine_st.child_processes.insert(pid, child);
+                let child_process_alloc: TypedArenaPtr<Child> =
+                    arena_alloc!(child, &mut self.machine_st.arena);
 
                 unify!(
                     self.machine_st,
                     pid_r,
-                    fixnum_as_cell!(Fixnum::build_with(pid))
+                    typed_arena_ptr_as_cell!(child_process_alloc)
                 );
 
                 Ok(())
@@ -8617,44 +8615,84 @@ impl Machine {
         })
     }
 
+    pub(crate) fn process_id(&mut self) -> CallResult {
+        fn stub_gen() -> Vec<FunctorElement> {
+            functor_stub(atom!("process_id"), 2)
+        }
+
+        // Process
+        let process_r = self.deref_register(1);
+        // Pid
+        let pid_r = self.deref_register(2);
+
+        let Some(ptr) = process_r.to_untyped_arena_ptr() else {
+            let err = self.machine_st.type_error(ValidType::Process, process_r);
+            return Err(self.machine_st.error_form(err, stub_gen()));
+        };
+
+        let process = match_untyped_arena_ptr!(ptr,
+            (ArenaHeaderTag::ChildProcess, child_process) => {
+                child_process
+            }
+            (ArenaHeaderTag::Dropped, _dropped) => {
+                let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+            _ => {
+                let err = self.machine_st.type_error(ValidType::Process, process_r);
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+        );
+
+        self.machine_st.bind(
+            pid_r.as_var().unwrap(),
+            fixnum_as_cell!(Fixnum::build_with(process.id())),
+        );
+
+        Ok(())
+    }
+
     pub(crate) fn process_wait(&mut self) -> CallResult {
         fn stub_gen() -> Vec<FunctorElement> {
             functor_stub(atom!("process_wait"), 2)
         }
 
-        // Pid
-        let pid_r = self.deref_register(1);
+        // Process
+        let process_r = self.deref_register(1);
         // Var | Status
         let status_r = self.deref_register(2);
         // timeout | 0
         let timeout_r = self.deref_register(3);
 
-        let Some(pid) = pid_r
-            .to_fixnum()
-            .and_then(|elem| elem.get_num().try_into().ok())
-        else {
-            let err = self
-                .machine_st
-                .existence_error(ExistenceError::Process(pid_r));
-            return Err(self.machine_st.error_form(err, stub_gen()));
-        };
-        let Some(child) = self.machine_st.child_processes.get_mut(&pid) else {
-            let err = self
-                .machine_st
-                .existence_error(ExistenceError::Process(pid_r));
+        let Some(ptr) = process_r.to_untyped_arena_ptr() else {
+            let err = self.machine_st.type_error(ValidType::Process, process_r);
             return Err(self.machine_st.error_form(err, stub_gen()));
         };
 
+        let mut process = match_untyped_arena_ptr!(ptr,
+            (ArenaHeaderTag::ChildProcess, child_process) => {
+                child_process
+            }
+            (ArenaHeaderTag::Dropped, _dropped) => {
+                let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+            _ => {
+                let err = self.machine_st.type_error(ValidType::Process, process_r);
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+        );
+
         let status = if let Some(atom) = timeout_r.to_atom() {
             match atom {
-                atom!("infinite") => child.wait().map(Some),
+                atom!("infinite") => process.wait().map(Some),
                 _ => {
                     panic!("Invalid Timeout value")
                 }
             }
         } else if let Some(timeout) = timeout_r.to_fixnum() {
             if timeout.get_num() == 0 {
-                child.try_wait()
+                process.try_wait()
             } else {
                 panic!("Invalid Timeout value")
             }
@@ -8728,23 +8766,28 @@ impl Machine {
         }
 
         // Pid
-        let pid_r = self.deref_register(1);
-        let Some(pid) = pid_r
-            .to_fixnum()
-            .and_then(|elem| elem.get_num().try_into().ok())
-        else {
-            let err = self
-                .machine_st
-                .existence_error(ExistenceError::Process(pid_r));
-            return Err(self.machine_st.error_form(err, stub_gen()));
-        };
-        let Some(child) = self.machine_st.child_processes.get_mut(&pid) else {
-            let err = self
-                .machine_st
-                .existence_error(ExistenceError::Process(pid_r));
+        let process_r = self.deref_register(1);
+
+        let Some(ptr) = process_r.to_untyped_arena_ptr() else {
+            let err = self.machine_st.type_error(ValidType::Process, process_r);
             return Err(self.machine_st.error_form(err, stub_gen()));
         };
-        if child.kill().is_err() {
+
+        let mut process = match_untyped_arena_ptr!(ptr,
+            (ArenaHeaderTag::ChildProcess, child_process) => {
+                child_process
+            }
+            (ArenaHeaderTag::Dropped, _dropped) => {
+                let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+            _ => {
+                let err = self.machine_st.type_error(ValidType::Process, process_r);
+                return Err(self.machine_st.error_form(err, stub_gen()));
+            }
+        );
+
+        if process.kill().is_err() {
             let perm_error =
                 self.machine_st
                     .permission_error(Permission::Modify, atom!("process"), stub_gen());
@@ -8758,18 +8801,23 @@ impl Machine {
             functor_stub(atom!("process_release"), 1)
         }
 
-        let pid_r = self.deref_register(1);
-        let Some(pid) = pid_r
-            .to_fixnum()
-            .and_then(|elem| elem.get_num().try_into().ok())
-        else {
-            let err = self
-                .machine_st
-                .existence_error(ExistenceError::Process(pid_r));
-            return Err(self.machine_st.error_form(err, stub_gen()));
-        };
-        self.machine_st.child_processes.remove(&pid);
-        Ok(())
+        let process = self.deref_register(1);
+
+        if let Some(ptr) = process.to_untyped_arena_ptr() {
+            match_untyped_arena_ptr!(ptr,
+                (ArenaHeaderTag::ChildProcess, child_process) => {
+                    child_process.drop_payload();
+
+                    return Ok(());
+                }
+                _ => {
+                }
+            );
+        }
+
+        let err = self.machine_st.type_error(ValidType::Process, process);
+
+        Err(self.machine_st.error_form(err, stub_gen()))
     }
 
     #[inline(always)]
index e301832bcdee6baabcf0fb2bba01fc339d09f319..104674193fc383bc002ad42b56e5f0cd049842ae 100644 (file)
@@ -218,6 +218,12 @@ macro_rules! match_untyped_arena_ptr_pat_body {
         #[allow(unused_braces)]
         $code
     }};
+    ($ptr:ident, ChildProcess, $listener:ident, $code:expr) => {{
+        #[allow(unused_mut)]
+        let mut $listener = unsafe { $ptr.as_typed_ptr::<std::process::Child>() };
+        #[allow(unused_braces)]
+        $code
+    }};
     ($ptr:ident, $($tags:tt)|+, $s:ident, $code:expr) => {{
         let $s = Stream::from_tag($ptr.get_tag(), $ptr);
         #[allow(unused_braces)]
diff --git a/tests/scryer/cli/unix/process.md b/tests/scryer/cli/unix/process.md
new file mode 100644 (file)
index 0000000..8a1aef0
--- /dev/null
@@ -0,0 +1,4 @@
+```trycmd
+$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("false", [], [process(P)]), process_wait(P, exit(1)), halt'
+
+```
diff --git a/tests/scryer/cli/windows/process.md b/tests/scryer/cli/windows/process.md
new file mode 100644 (file)
index 0000000..c83520c
--- /dev/null
@@ -0,0 +1,4 @@
+```trycmd
+$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("cmd", ["/C", "exit", "1"], [process(P)]), process_wait(P, exit(1)), halt'
+
+```
index a501bd423e79cf71c3ca4738efb884be88fa9bbc..19c78c5959069ea9e3ba815c07e15975c28a3933 100644 (file)
@@ -19,9 +19,19 @@ mod src_tests;
     ignore = "miri isolation, unsupported operation: can't call foreign function"
 )]
 fn cli_tests() {
-    trycmd::TestCases::new()
+    let cases = trycmd::TestCases::new();
+    cases
         .default_bin_name("scryer-prolog")
         .case("tests/scryer/cli/issues/*.toml")
-        .case("tests/scryer/cli/src_tests/*.toml")
-        .case("tests/scryer/cli/src_tests/*.md");
+        .case("tests/scryer/cli/src_tests/*.toml");
+
+    #[cfg(windows)]
+    {
+        cases.case("tests/scryer/cli/windows/*.md");
+    }
+
+    #[cfg(unix)]
+    {
+        cases.case("tests/scryer/cli/unix/*.md");
+    }
 }