├── .gitignore ├── .merlin ├── .travis.yml ├── CHANGES ├── META ├── Makefile ├── README.md ├── _tags ├── lib ├── _tags ├── process.ml └── process.mli ├── lib_test ├── _tags ├── empty_out.ml ├── interleave_err_out.ml ├── kilobyte_writer.ml ├── megabyte_writer.ml ├── nl_out.ml ├── start_nl_out.ml ├── test.ml └── trail_nl_out.ml └── opam /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | _build 3 | _tests 4 | *.native 5 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | PKG alcotest 2 | 3 | S lib 4 | B _build/** 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: required 3 | install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-opam.sh 4 | script: bash -ex .travis-opam.sh 5 | env: 6 | - OCAML_VERSION=4.01 PACKAGE=process 7 | - OCAML_VERSION=4.02 PACKAGE=process 8 | - OCAML_VERSION=4.03 PACKAGE=process 9 | - OCAML_VERSION=4.04 PACKAGE=process 10 | os: 11 | - linux 12 | - osx 13 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.2.1 (2016-09-01): 2 | * Added Windows support (#5 from msprotz) 3 | * Now installs .cmx files (#4 from aantron) 4 | 5 | 0.2.0 (2016-01-17): 6 | * Fixed assumptions regarding subprocess I/O (#2 from aantron) 7 | * Added Process.Exit.error_to_string 8 | 9 | 0.1.0 (2015-12-25): 10 | * Initial public release 11 | -------------------------------------------------------------------------------- /META: -------------------------------------------------------------------------------- 1 | version = "0.2.1" 2 | description = "Easy process control" 3 | requires = "unix" 4 | archive(byte) = "process.cma" 5 | archive(byte, plugin) = "process.cma" 6 | archive(native) = "process.cmxa" 7 | archive(native, plugin) = "process.cmxs" 8 | exists_if = "process.cma" 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test install uninstall reinstall clean 2 | 3 | FINDLIB_NAME=process 4 | MOD_NAME=process 5 | 6 | OCAMLBUILD=ocamlbuild -use-ocamlfind -classic-display 7 | 8 | TARGETS=.cma .cmxa .cmx 9 | 10 | PRODUCTS=$(addprefix $(MOD_NAME),$(TARGETS)) 11 | 12 | TYPES=.mli .cmi .cmti 13 | 14 | INSTALL:=$(addprefix $(MOD_NAME), $(TYPES)) \ 15 | $(addprefix $(MOD_NAME), $(TARGETS)) 16 | 17 | INSTALL:=$(addprefix _build/lib/,$(INSTALL)) 18 | 19 | ARCHIVES:=_build/lib/$(MOD_NAME).a 20 | 21 | build: 22 | $(OCAMLBUILD) $(PRODUCTS) 23 | 24 | test_%.native: lib_test/%.ml 25 | $(OCAMLBUILD) lib_test/$*.native 26 | mv $*.native test_$*.native 27 | 28 | TEST_HELPERS=\ 29 | megabyte_writer kilobyte_writer\ 30 | empty_out nl_out trail_nl_out start_nl_out interleave_err_out 31 | 32 | test: build $(addprefix test_,$(addsuffix .native, $(TEST_HELPERS))) 33 | $(OCAMLBUILD) lib_test/test.native 34 | ./test.native 35 | 36 | install: 37 | ocamlfind install $(FINDLIB_NAME) META \ 38 | $(INSTALL) \ 39 | $(ARCHIVES) 40 | 41 | uninstall: 42 | ocamlfind remove $(FINDLIB_NAME) 43 | 44 | reinstall: uninstall install 45 | 46 | clean: 47 | ocamlbuild -clean 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Easy process control in OCaml 2 | 3 | ```ocaml 4 | let stdout_lines = Process.read_stdout "ls" [||] in 5 | List.iter print_endline stdout_lines 6 | ``` 7 | 8 | It's as easy as that! 9 | 10 | Exit status, stdin, and stderr are also available. 11 | 12 | `process` makes it easy to use commands like functions. 13 | -------------------------------------------------------------------------------- /_tags: -------------------------------------------------------------------------------- 1 | true: warn(@5@8@10@11@12@14@23@24@26@29) 2 | true: principal, bin_annot, safe_string, strict_sequence, debug 3 | 4 | "lib": include 5 | 6 | -------------------------------------------------------------------------------- /lib/_tags: -------------------------------------------------------------------------------- 1 | <*.*>: package(bytes) 2 | -------------------------------------------------------------------------------- /lib/process.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015 David Sheets 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | 18 | module Signal = struct 19 | type t = 20 | | SIGABRT 21 | | SIGALRM 22 | | SIGFPE 23 | | SIGHUP 24 | | SIGILL 25 | | SIGINT 26 | | SIGKILL 27 | | SIGPIPE 28 | | SIGQUIT 29 | | SIGSEGV 30 | | SIGTERM 31 | | SIGUSR1 32 | | SIGUSR2 33 | | SIGCHLD 34 | | SIGCONT 35 | | SIGSTOP 36 | | SIGTSTP 37 | | SIGTTIN 38 | | SIGTTOU 39 | | SIGVTALRM 40 | | SIGPROF 41 | | Unknown of int 42 | 43 | let of_int = Sys.(function 44 | | x when x = sigabrt -> SIGABRT 45 | | x when x = sigalrm -> SIGALRM 46 | | x when x = sigfpe -> SIGFPE 47 | | x when x = sighup -> SIGHUP 48 | | x when x = sigill -> SIGILL 49 | | x when x = sigint -> SIGINT 50 | | x when x = sigkill -> SIGKILL 51 | | x when x = sigpipe -> SIGPIPE 52 | | x when x = sigquit -> SIGQUIT 53 | | x when x = sigsegv -> SIGSEGV 54 | | x when x = sigterm -> SIGTERM 55 | | x when x = sigusr1 -> SIGUSR1 56 | | x when x = sigusr2 -> SIGUSR2 57 | | x when x = sigchld -> SIGCHLD 58 | | x when x = sigcont -> SIGCONT 59 | | x when x = sigstop -> SIGSTOP 60 | | x when x = sigtstp -> SIGTSTP 61 | | x when x = sigttin -> SIGTTIN 62 | | x when x = sigttou -> SIGTTOU 63 | | x when x = sigvtalrm -> SIGVTALRM 64 | | x when x = sigprof -> SIGPROF 65 | | x -> Unknown x 66 | ) 67 | 68 | let to_string = function 69 | | SIGABRT -> "SIGABRT" 70 | | SIGALRM -> "SIGALRM" 71 | | SIGFPE -> "SIGFPE" 72 | | SIGHUP -> "SIGHUP" 73 | | SIGILL -> "SIGILL" 74 | | SIGINT -> "SIGINT" 75 | | SIGKILL -> "SIGKILL" 76 | | SIGPIPE -> "SIGPIPE" 77 | | SIGQUIT -> "SIGQUIT" 78 | | SIGSEGV -> "SIGSEGV" 79 | | SIGTERM -> "SIGTERM" 80 | | SIGUSR1 -> "SIGUSR1" 81 | | SIGUSR2 -> "SIGUSR2" 82 | | SIGCHLD -> "SIGCHLD" 83 | | SIGCONT -> "SIGCONT" 84 | | SIGSTOP -> "SIGSTOP" 85 | | SIGTSTP -> "SIGTSTP" 86 | | SIGTTIN -> "SIGTTIN" 87 | | SIGTTOU -> "SIGTTOU" 88 | | SIGVTALRM -> "SIGVTALRM" 89 | | SIGPROF -> "SIGPROF" 90 | | Unknown k -> "SIG"^(string_of_int k) 91 | 92 | end 93 | 94 | module Exit = struct 95 | type t = 96 | | Exit of int 97 | | Kill of Signal.t 98 | | Stop of Signal.t 99 | 100 | type error = { 101 | cwd : string; 102 | command : string; 103 | args : string array; 104 | status : t; 105 | } 106 | exception Error of error 107 | 108 | let of_unix = Unix.(function 109 | | WEXITED k -> Exit k 110 | | WSIGNALED k -> Kill (Signal.of_int k) 111 | | WSTOPPED k -> Stop (Signal.of_int k) 112 | ) 113 | 114 | let to_string = function 115 | | Exit k -> Printf.sprintf "exit %d" k 116 | | Kill k -> Printf.sprintf "kill %s" (Signal.to_string k) 117 | | Stop k -> Printf.sprintf "stop %s" (Signal.to_string k) 118 | 119 | let error_to_string { cwd; command; args; status } = 120 | let args = Array.map (Printf.sprintf "%S") args in 121 | let args_s = String.concat "; " (Array.to_list args) in 122 | Printf.sprintf "%s [|%s|] in %s: %s" command args_s cwd (to_string status) 123 | 124 | let check ?(exit_status=[0]) command args = function 125 | | Exit k when List.mem k exit_status -> () 126 | | status -> 127 | raise (Error { cwd = Unix.getcwd (); command; args; status }) 128 | 129 | end 130 | 131 | module Output = struct 132 | type t = { 133 | exit_status : Exit.t; 134 | stdout : string list; 135 | stderr : string list; 136 | } 137 | end 138 | 139 | module type S = sig 140 | type 'a io 141 | 142 | val run : 143 | ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array 144 | -> Output.t io 145 | 146 | val read_stdout : 147 | ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array 148 | -> string list io 149 | end 150 | 151 | module Blocking : S with type 'a io = 'a = struct 152 | type 'a io = 'a 153 | 154 | let quote = Printf.sprintf "\"%s\"" 155 | 156 | let string_of_prog_args prog args = 157 | prog ^ ( 158 | if Array.length args > 0 then 159 | " " ^ (String.concat " " Array.(to_list (map quote args))) 160 | else "" 161 | ) 162 | 163 | let rec waitpid_retry flags pid = 164 | try Unix.waitpid flags pid 165 | with Unix.Unix_error (Unix.EINTR,"waitpid","") -> 166 | waitpid_retry flags pid 167 | 168 | let io_from_fd fds fn fd = 169 | let closed = fn fd in 170 | if closed 171 | then List.filter ((<>) fd) fds 172 | else fds 173 | 174 | let select_io 175 | ~input_stdout ~stdout 176 | ~input_stderr ~stderr 177 | ~output_stdin ~stdin 178 | ~read_fds ~write_fds = 179 | let rec loop ~read_fds ~write_fds = 180 | if read_fds <> [] || write_fds <> [] 181 | then 182 | let ready_read, ready_write, _ready_exn = 183 | Unix.select read_fds write_fds [] ~-.1. 184 | in 185 | match ready_read with 186 | | fd::_ when fd = stdout -> 187 | let read_fds = io_from_fd read_fds input_stdout fd in 188 | loop ~read_fds ~write_fds 189 | | fd::_ when fd = stderr -> 190 | let read_fds = io_from_fd read_fds input_stderr fd in 191 | loop ~read_fds ~write_fds 192 | | _::_ -> failwith "unexpected read fd" (* TODO: ? *) 193 | | [] -> match ready_write with 194 | | fd::_ -> 195 | let write_fds = io_from_fd write_fds output_stdin fd in 196 | loop ~read_fds ~write_fds 197 | | [] -> failwith "select failed" (* TODO: ? *) 198 | in 199 | try 200 | let sigpipe = Sys.(signal sigpipe Signal_ignore) in 201 | loop ~read_fds ~write_fds; 202 | Sys.(set_signal sigpipe) sigpipe 203 | with Invalid_argument _ -> 204 | (* Can't ignore the pipe broken signal on Windows. *) 205 | loop ~read_fds ~write_fds 206 | 207 | let execute prog args ~output_stdin ~input_stdout ~input_stderr = 208 | let in_fd, stdin = Unix.pipe () in 209 | let stdout, out_fd = Unix.pipe () in 210 | let stderr, err_fd = Unix.pipe () in 211 | Unix.set_close_on_exec stdin; 212 | Unix.set_close_on_exec stdout; 213 | Unix.set_close_on_exec stderr; 214 | let args = Array.append [|prog|] args in 215 | let pid = Unix.create_process prog args in_fd out_fd err_fd in 216 | Unix.close in_fd; 217 | Unix.close out_fd; 218 | Unix.close err_fd; 219 | select_io 220 | ~input_stdout ~stdout 221 | ~input_stderr ~stderr 222 | ~output_stdin ~stdin 223 | ~read_fds:[ stdout; stderr ] ~write_fds:[ stdin ]; 224 | (* stdin is closed when we run out of input *) 225 | Unix.close stdout; 226 | Unix.close stderr; 227 | let status = snd (waitpid_retry [Unix.WUNTRACED] pid) in 228 | Exit.of_unix status 229 | 230 | let rindex_from buf i c = 231 | try Some (Bytes.rindex_from buf i c) with Not_found -> None 232 | 233 | let rec lines buf i acc = 234 | match rindex_from buf i '\n' with 235 | | Some 0 -> Bytes.empty :: (Bytes.sub buf 1 i) :: acc 236 | | Some j -> lines buf (j - 1) (Bytes.sub buf (j + 1) (i - j) :: acc) 237 | | None -> Bytes.sub buf 0 (i + 1) :: acc 238 | 239 | let read_lines buf len into fd = 240 | (* The EPIPE case covers an odd behavior on Windows. 241 | See . 242 | *) 243 | let n = Unix.(try read fd buf 0 len with Unix_error (EPIPE, _, _) -> 0) in 244 | if n = 0 245 | then true (* closed *) 246 | else 247 | let ls = lines buf (n - 1) [] in 248 | begin match !into with 249 | | [] -> into := List.rev ls 250 | | partial_line::rest -> match ls with 251 | | [] -> () 252 | | first::more -> 253 | let first = Bytes.cat partial_line first in 254 | into := List.rev_append more (first :: rest) 255 | end; 256 | false (* not closed *) 257 | 258 | let run ?(stdin=Bytes.empty) ?exit_status prog args = 259 | let out_lines = ref [] in 260 | let err_lines = ref [] in 261 | let len = 4096 in 262 | let buf = Bytes.create len in 263 | let input_stdout = read_lines buf len out_lines in 264 | let input_stderr = read_lines buf len err_lines in 265 | let stdin_len = Bytes.length stdin in 266 | let stdin_off = ref 0 in 267 | let output_stdin i_fd = 268 | let off = !stdin_off in 269 | let len = stdin_len - off in 270 | if len = 0 271 | then begin 272 | Unix.close i_fd; 273 | true (* closed, we have nothing more to write *) 274 | end 275 | else 276 | try 277 | let n = Unix.single_write i_fd stdin off len in 278 | stdin_off := off + n; 279 | false (* not closed *) 280 | with 281 | | Unix.Unix_error (Unix.EPIPE, "single_write", _) -> true (* closed *) 282 | in 283 | let exit_status' = 284 | execute prog args ~output_stdin ~input_stdout ~input_stderr 285 | in 286 | (match exit_status with 287 | | None -> () 288 | | Some exit_status -> Exit.check ~exit_status prog args exit_status' 289 | ); 290 | let exit_status = exit_status' in 291 | let stdout = List.rev_map Bytes.to_string !out_lines in 292 | let stderr = List.rev_map Bytes.to_string !err_lines in 293 | Output.({ exit_status; stdout; stderr; }) 294 | 295 | let read_stdout ?stdin ?exit_status prog args = 296 | let exit_status = match exit_status with None -> [0] | Some v -> v in 297 | (run ?stdin ~exit_status prog args).Output.stdout 298 | 299 | end 300 | 301 | include Blocking 302 | -------------------------------------------------------------------------------- /lib/process.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015 David Sheets 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | 18 | module Signal : sig 19 | type t = 20 | | SIGABRT 21 | | SIGALRM 22 | | SIGFPE 23 | | SIGHUP 24 | | SIGILL 25 | | SIGINT 26 | | SIGKILL 27 | | SIGPIPE 28 | | SIGQUIT 29 | | SIGSEGV 30 | | SIGTERM 31 | | SIGUSR1 32 | | SIGUSR2 33 | | SIGCHLD 34 | | SIGCONT 35 | | SIGSTOP 36 | | SIGTSTP 37 | | SIGTTIN 38 | | SIGTTOU 39 | | SIGVTALRM 40 | | SIGPROF 41 | | Unknown of int 42 | 43 | val of_int : int -> t 44 | 45 | val to_string : t -> string 46 | 47 | end 48 | 49 | module Exit : sig 50 | type t = 51 | | Exit of int 52 | | Kill of Signal.t 53 | | Stop of Signal.t 54 | 55 | type error = { 56 | cwd : string; 57 | command : string; 58 | args : string array; 59 | status : t; 60 | } 61 | 62 | exception Error of error 63 | 64 | val of_unix : Unix.process_status -> t 65 | 66 | val to_string : t -> string 67 | 68 | val error_to_string : error -> string 69 | end 70 | 71 | module Output : sig 72 | type t = { 73 | exit_status : Exit.t; 74 | stdout : string list; 75 | stderr : string list; 76 | } 77 | end 78 | 79 | module type S = sig 80 | type 'a io 81 | 82 | val run : 83 | ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array 84 | -> Output.t io 85 | 86 | val read_stdout : 87 | ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array 88 | -> string list io 89 | end 90 | 91 | include S with type 'a io = 'a 92 | -------------------------------------------------------------------------------- /lib_test/_tags: -------------------------------------------------------------------------------- 1 | <*.*>: package(alcotest) 2 | -------------------------------------------------------------------------------- /lib_test/empty_out.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsheets/ocaml-process/1793f70e7239c7abe0e26ae3e2bb93bab09f88e4/lib_test/empty_out.ml -------------------------------------------------------------------------------- /lib_test/interleave_err_out.ml: -------------------------------------------------------------------------------- 1 | print_endline "longish"; 2 | prerr_endline "short"; 3 | print_endline "longerer"; 4 | prerr_endline "very long indeed"; 5 | print_endline "short"; 6 | -------------------------------------------------------------------------------- /lib_test/kilobyte_writer.ml: -------------------------------------------------------------------------------- 1 | let kilobyte = String.make 1024 ' ' 2 | ;; 3 | print_string kilobyte 4 | -------------------------------------------------------------------------------- /lib_test/megabyte_writer.ml: -------------------------------------------------------------------------------- 1 | let megabyte = String.make (1024 * 1024) ' ' 2 | ;; 3 | print_string megabyte 4 | -------------------------------------------------------------------------------- /lib_test/nl_out.ml: -------------------------------------------------------------------------------- 1 | print_endline "" 2 | -------------------------------------------------------------------------------- /lib_test/start_nl_out.ml: -------------------------------------------------------------------------------- 1 | Printf.printf "\nhello, world" 2 | -------------------------------------------------------------------------------- /lib_test/test.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2016 David Sheets 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | 18 | let try_test f () = 19 | try f () 20 | with Process.Exit.Error e -> failwith (Process.Exit.error_to_string e) 21 | 22 | module Pipes = struct 23 | 24 | let full_pipe () = 25 | let megabyte = Bytes.make (1024 * 1024) ' ' in 26 | Process.read_stdout 27 | ~stdin:megabyte "./test_megabyte_writer.native" [||] |> ignore 28 | 29 | let broken_pipe () = 30 | let megabyte = Bytes.make (1024 * 1024) ' ' in 31 | Process.read_stdout 32 | ~stdin:megabyte "./test_kilobyte_writer.native" [||] |> ignore 33 | 34 | let tests = [ 35 | "full_pipe", `Quick, try_test full_pipe; 36 | "broken_pipe", `Quick, try_test broken_pipe; 37 | ] 38 | end 39 | 40 | module Reading = struct 41 | 42 | let empty () = 43 | let output = Process.read_stdout "./test_empty_out.native" [||] in 44 | Alcotest.(check (list string)) "empty output" [] output 45 | 46 | let nl () = 47 | let output = Process.read_stdout "./test_nl_out.native" [||] in 48 | Alcotest.(check (list string)) "newline output" ["";""] output 49 | 50 | let trail_nl () = 51 | let output = Process.read_stdout "./test_trail_nl_out.native" [||] in 52 | Alcotest.(check (list string)) "trailing newline output" 53 | ["hello, world";""] output 54 | 55 | let start_nl () = 56 | let output = Process.read_stdout "./test_start_nl_out.native" [||] in 57 | Alcotest.(check (list string)) "starting newline output" 58 | [""; "hello, world"] output 59 | 60 | let interleave () = 61 | (* TODO: test the stderr, too *) 62 | let output = Process.read_stdout "./test_interleave_err_out.native" [||] in 63 | Alcotest.(check (list string)) "interleaved err and out output" 64 | ["longish"; "longerer"; "short"; ""] output 65 | 66 | let tests = [ 67 | "empty", `Quick, try_test empty; 68 | "nl", `Quick, try_test nl; 69 | "trail_nl", `Quick, try_test trail_nl; 70 | "start_nl", `Quick, try_test start_nl; 71 | "interleave", `Quick, try_test interleave; 72 | ] 73 | end 74 | 75 | let tests = [ 76 | "pipes", Pipes.tests; 77 | "reading", Reading.tests; 78 | ] 79 | 80 | ;; 81 | Alcotest.run "process" tests 82 | -------------------------------------------------------------------------------- /lib_test/trail_nl_out.ml: -------------------------------------------------------------------------------- 1 | print_endline "hello, world" 2 | -------------------------------------------------------------------------------- /opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | name: "process" 3 | version: "0.2.1" 4 | maintainer: "David Sheets " 5 | authors: [ "David Sheets" "Jonathan Protzenko" ] 6 | homepage: "https://github.com/dsheets/ocaml-process" 7 | bug-reports: "https://github.com/dsheets/ocaml-process/issues" 8 | license: "ISC" 9 | dev-repo: "https://github.com/dsheets/ocaml-process.git" 10 | tags: [ "process" "subprocess" "command" "system" ] 11 | build: [ 12 | [make] 13 | ] 14 | install: [make "install"] 15 | remove: ["ocamlfind" "remove" "process"] 16 | depends: [ 17 | "ocamlfind" {build} 18 | "ocamlbuild" {build} 19 | "base-unix" 20 | "base-bytes" 21 | ] 22 | available: [ ocaml-version >= "4.01.0" ] 23 | --------------------------------------------------------------------------------