open Lwt.Infix module F = Mirage_flow_combinators.F let fail fmt = Fmt.kstr (fun s -> Alcotest.fail s) fmt (* this is a very small set of tests for the channel interface, intended to ensure that EOF conditions on the underlying flow are handled properly *) module Channel = Mirage_channel.Make(F) let check_eof = function | Ok (`Data ch) -> fail "character %c was returned from Channel.read_char on an empty flow" ch | Ok `Eof -> Lwt.return () | Error e -> fail "unexpected error: %a" Channel.pp_error e let err_no_exception () = fail "no exception" let err_wrong_exception e = fail "wrong exception: %s" (Printexc.to_string e) let test_read_char_eof () = let f = F.make () in let c = Channel.create f in Channel.read_char c >>= check_eof let test_read_line () = let input = "I am the very model of a modern major general" in let f = F.make ~input:(F.input_string input) () in let c = Channel.create f in Channel.read_line c >|= function | Ok (`Data buf) -> Alcotest.(check string) "read line" input (Cstruct.copyv buf) | Ok `Eof -> fail "eof" | Error e -> fail "error: %a" Channel.pp_error e (* The line is longer than the limit *) let test_read_line_len () = let input = "I am the very model of a modern major general" in let f = F.make ~input:(F.input_string input) () in let c = Channel.create f in Channel.read_line ~len:5 c >|= function | Ok (`Data _) -> fail "read a line which was too big" | Ok `Eof -> fail "eof" | Error _ -> () (* The line is shorter than the limit and bounded by \r\n *) let test_read_line_len2 () = let input = "I\r\n am the very model of a modern major general" in let f = F.make ~input:(F.input_string input) () in let c = Channel.create f in Channel.read_line ~len:5 c >|= function | Ok (`Data buf) -> Alcotest.(check string) "read line" "I" (Cstruct.copyv buf) | Ok `Eof -> fail "eof" | Error e -> fail "error: %a" Channel.pp_error e (* The line is shorter than the limit and bounded by EOF *) let test_read_line_len3 () = let input = "I am the very model of a modern major general" in let f = F.make ~input:(F.input_string input) () in let c = Channel.create f in Channel.read_line ~len:50 c >|= function | Ok (`Data buf) -> Alcotest.(check string) "read line" input (Cstruct.copyv buf) | Ok `Eof -> fail "eof" | Error e -> fail "error: %a" Channel.pp_error e type channel = V : (module Mirage_channel.S with type t = 'a and type error = [> `Line_too_long ]) * 'a -> channel let channel_from_raw_string s = let consumed = ref false in let module Flow = struct type flow = unit type error = | type write_error = Mirage_flow.write_error let pp_error : error Fmt.t = fun _ -> function _ -> . let pp_write_error : Mirage_flow.write_error Fmt.t = fun ppf `Closed -> Fmt.string ppf "Flow closed" let read () = if not !consumed then ( consumed := true ; Lwt.return_ok (`Data (Cstruct.of_string s)) ) else Lwt.return_ok `Eof let write _ _ = assert false let writev _ _ = assert false let close _ = Lwt.return () let shutdown _ _ = Lwt.return_unit end in let module Channel = Mirage_channel.Make(Flow) in V ((module Channel), Channel.create ()) let test_read_line_len4 () = let V ((module Channel), c) = channel_from_raw_string "foo" in Channel.read_line ~len:3 c >|= function | Ok (`Data bufs) -> fail "Unexpected data: %S" Cstruct.(to_string (concat bufs)) | Ok `Eof -> fail "eof" | Error e -> match e with | `Line_too_long -> () | e -> fail "Unexpected error: %a" Channel.pp_error e let test_read_line_len5 () = let V ((module Channel), c) = channel_from_raw_string "foo\r" in Channel.read_line ~len:3 c >|= function | Ok (`Data bufs) -> fail "Unexpected data: %S" Cstruct.(to_string (concat bufs)) | Ok `Eof -> fail "eof" | Error e -> match e with | `Line_too_long -> () | e -> fail "Unexpected error: %a" Channel.pp_error e let test_read_line_len6 () = let V ((module Channel), c) = channel_from_raw_string "foo\r\n" in Channel.read_line ~len:3 c >|= function | Ok (`Data bufs) -> fail "Unexpected data: %S" Cstruct.(to_string (concat bufs)) | Ok `Eof -> fail "eof" | Error e -> match e with | `Line_too_long -> () | e -> fail "Unexpected error: %a" Channel.pp_error e let test_read_line_len7 () = let V ((module Channel), c) = channel_from_raw_string "foo\r\n" in Channel.read_line ~len:4 c >|= function | Ok (`Data bufs) -> fail "Unexpected data: %S" Cstruct.(to_string (concat bufs)) | Ok `Eof -> fail "eof" | Error e -> match e with | `Line_too_long -> () | e -> fail "Unexpected error: %a" Channel.pp_error e let test_read_line_len8 () = let V ((module Channel), c) = channel_from_raw_string "foo\r\n" in Channel.read_line ~len:5 c >|= function | Ok (`Data bufs) -> Alcotest.(check string) "read line" "foo" Cstruct.(to_string (concat bufs)) | Ok `Eof -> fail "eof" | Error e -> fail "Unexpected error: %a" Channel.pp_error e let test_read_exactly () = let input = "I am the very model of a modern major general" in let f = F.make ~input:(F.input_string input) () in let c = Channel.create f in Channel.read_exactly ~len:4 c >|= function | Ok (`Data bufs) -> Alcotest.(check int) "wrong length" 4 (Cstruct.(length (concat bufs))) | Ok `Eof -> fail "eof" | Error e -> fail "error: %a" Channel.pp_error e let test_read_until_eof_then_write () = let str = "I am the very model of a modern major general" in let closed = ref false in let output _buf _off len = if !closed then Alcotest.fail "attempted to write after the flow was closed" else Lwt.return len in let close () = closed := true; Lwt.return_unit in let input = F.input_string str in let f = F.make ~close ~input ~output () in let c = Channel.create f in (* Should read to EOF: *) Channel.read_line c >>= fun _ -> Channel.write_line c "Even though I've read to EOF, I should be able to write"; Channel.flush c >|= function | Ok () -> () | Error `Closed -> fail "error: closed" | Error e -> fail "error: %a" Channel.pp_write_error e let suite = [ "read_char + EOF" , `Quick, test_read_char_eof; "read_line" , `Quick, test_read_line; "read_exactly" , `Quick, test_read_exactly; "write after read EOF", `Quick, test_read_until_eof_then_write; "read_line_len" , `Quick, test_read_line_len; "read_line_len2" , `Quick, test_read_line_len2; "read_line_len3" , `Quick, test_read_line_len3; "read_line_len4" , `Quick, test_read_line_len4; "read_line_len5" , `Quick, test_read_line_len5; "read_line_len6" , `Quick, test_read_line_len6; "read_line_len7" , `Quick, test_read_line_len7; "read_line_len8" , `Quick, test_read_line_len8; ]