Skip to content

Commit

Permalink
split test_httpaf into one file for each module
Browse files Browse the repository at this point in the history
The primary goal is to split up the client connection and server
connection tests, since it's very easy to jump around in test_httpaf.ml
as it was without realizing you jumped from one module to the other and
now you're looking at a completely different kind of test.
  • Loading branch information
dpatti committed May 9, 2020
1 parent 90b8d98 commit 3b1e85b
Show file tree
Hide file tree
Showing 11 changed files with 1,014 additions and 1,017 deletions.
12 changes: 11 additions & 1 deletion lib_test/dune
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
(executables
(libraries bigstringaf httpaf alcotest)
(modules test_httpaf)
(modules
helpers
test_client_connection
test_headers
test_httpaf
test_iovec
test_method
test_request
test_response
test_server_connection
test_version)
(names test_httpaf))

(alias
Expand Down
63 changes: 63 additions & 0 deletions lib_test/helpers.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
open Httpaf

let maybe_serialize_body f body =
match body with
| None -> ()
| Some body -> Faraday.write_string f body

let request_to_string ?body r =
let f = Faraday.create 0x1000 in
Httpaf_private.Serialize.write_request f r;
maybe_serialize_body f body;
Faraday.serialize_to_string f

let response_to_string ?body r =
let f = Faraday.create 0x1000 in
Httpaf_private.Serialize.write_response f r;
maybe_serialize_body f body;
Faraday.serialize_to_string f

module Read_operation = struct
type t = [ `Read | `Yield | `Close ]

let pp_hum fmt (t : t) =
let str =
match t with
| `Read -> "Read"
| `Yield -> "Yield"
| `Close -> "Close"
in
Format.pp_print_string fmt str
;;
end

module Write_operation = struct
type t = [ `Write of Bigstringaf.t IOVec.t list | `Yield | `Close of int ]

let iovecs_to_string iovecs =
let len = IOVec.lengthv iovecs in
let bytes = Bytes.create len in
let dst_off = ref 0 in
List.iter (fun { IOVec.buffer; off = src_off; len } ->
Bigstringaf.unsafe_blit_to_bytes buffer ~src_off bytes ~dst_off:!dst_off ~len;
dst_off := !dst_off + len)
iovecs;
Bytes.unsafe_to_string bytes
;;

let pp_hum fmt (t : t) =
match t with
| `Write iovecs -> Format.fprintf fmt "Write %S" (iovecs_to_string iovecs)
| `Yield -> Format.pp_print_string fmt "Yield"
| `Close len -> Format.fprintf fmt "Close %i" len
;;

let to_write_as_string t =
match t with
| `Write iovecs -> Some (iovecs_to_string iovecs)
| `Close _ | `Yield -> None
;;
end

let write_operation = Alcotest.of_pp Write_operation.pp_hum
let read_operation = Alcotest.of_pp Read_operation.pp_hum
200 changes: 200 additions & 0 deletions lib_test/test_client_connection.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
open Httpaf
open Helpers
open Client_connection

module Response = struct
include Response

let pp = pp_hum
let equal x y = x = y
end

let feed_string t str =
let len = String.length str in
let input = Bigstringaf.of_string str ~off:0 ~len in
read t input ~off:0 ~len

let read_string t str =
let c = feed_string t str in
Alcotest.(check int) "read consumes all input" (String.length str) c;
;;

let read_response t r =
let request_string = response_to_string r in
read_string t request_string
;;

let reader_ready t =
Alcotest.check read_operation "Reader is ready"
`Read (next_read_operation t :> [`Close | `Read | `Yield]);
;;

let write_string ?(msg="output written") t str =
let len = String.length str in
Alcotest.(check (option string)) msg
(Some str)
(next_write_operation t |> Write_operation.to_write_as_string);
report_write_result t (`Ok len);
;;

let write_request ?(msg="request written") t r =
let request_string = request_to_string r in
write_string ~msg t request_string
;;

let writer_yielded t =
Alcotest.check write_operation "Writer is in a yield state"
`Yield (next_write_operation t);
;;

let writer_closed t =
Alcotest.check write_operation "Writer is closed"
(`Close 0) (next_write_operation t);
;;

let connection_is_shutdown t =
Alcotest.check read_operation "Reader is closed"
`Close (next_read_operation t :> [`Close | `Read | `Yield]);
writer_closed t;
;;

let default_response_handler expected_response response body =
Alcotest.check (module Response) "expected response" expected_response response;
let on_read _ ~off:_ ~len:_ = () in
let on_eof () = () in
Body.schedule_read body ~on_read ~on_eof;
;;

let no_error_handler _ = assert false

let test_get () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in

(* Single GET *)
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.close_writer body;
write_request t request';
writer_closed t;
read_response t response;

(* Single GET, reponse closes connection *)
let response =
Response.create `OK ~headers:(Headers.of_list [ "connection", "close" ])
in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.close_writer body;
write_request t request';
read_response t response;
let c = read_eof t Bigstringaf.empty ~off:0 ~len:0 in
Alcotest.(check int) "read_eof with no input returns 0" 0 c;
connection_is_shutdown t;

(* Single GET, streaming body *)
let response =
Response.create `OK ~headers:(Headers.of_list [ "transfer-encoding", "chunked" ])
in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.close_writer body;
write_request t request';
read_response t response;
read_string t "d\r\nHello, world!\r\n0\r\n\r\n";
;;

let test_response_eof () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)

let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Malformed_response msg -> error_message := Some msg
| _ -> assert false)
in
Body.close_writer body;
write_request t request';
writer_closed t;
reader_ready t;
let c = read_eof t Bigstringaf.empty ~off:0 ~len:0 in
Alcotest.(check int) "read_eof with no input returns 0" 0 c;
connection_is_shutdown t;
Alcotest.(check (option string)) "unexpected eof"
(Some "unexpected eof")
!error_message
;;

let test_report_exn () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)

let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Exn (Failure msg) -> error_message := Some msg
| _ -> assert false)
in
Body.close_writer body;
write_request t request';
writer_closed t;
reader_ready t;
report_exn t (Failure "something went wrong");
connection_is_shutdown t;
Alcotest.(check (option string)) "something went wrong"
(Some "something went wrong")
!error_message
;;

let test_input_shrunk () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)

let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Exn (Failure msg) -> error_message := Some msg
| _ -> assert false)
in
Body.close_writer body;
write_request t request';
writer_closed t;
reader_ready t;
let c = feed_string t "HTTP/1.1 200 OK\r\nDate" in
Alcotest.(check int) "read the status line" c 17;
report_exn t (Failure "something went wrong");
connection_is_shutdown t;
Alcotest.(check (option string)) "something went wrong"
(Some "something went wrong")
!error_message
;;


let tests =
[ "GET" , `Quick, test_get
; "Response EOF", `Quick, test_response_eof
; "report_exn" , `Quick, test_report_exn
; "input_shrunk", `Quick, test_input_shrunk
]
46 changes: 46 additions & 0 deletions lib_test/test_headers.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
open Httpaf

let check msg ~expect actual =
Alcotest.(check (list (pair string string))) msg expect (Headers.to_list actual)
;;

let test_replace () =
check "replace trailing element"
~expect:["c", "d"; "a", "d"]
(Headers.replace
(Headers.of_list ["c", "d"; "a", "b"])
"a"
"d");

check "replace middle element"
~expect:["e", "f"; "c", "z"; "a", "b"]
(Headers.replace
(Headers.of_list ["e", "f"; "c", "d"; "a", "b"])
"c"
"z");

check "remove multiple trailing elements"
~expect:["c", "d"; "a", "d"]
(Headers.replace
(Headers.of_list [ "c", "d"; "a", "b"; "a", "c"])
"a"
"d");
;;

let test_remove () =
check "remove leading element"
~expect:["c", "d"]
(Headers.remove
(Headers.of_list ["a", "b"; "c", "d"])
"a");
check "remove trailing element"
~expect:["c", "d"]
(Headers.remove
(Headers.of_list ["c", "d"; "a", "b"])
"a");
;;

let tests =
[ "remove" , `Quick, test_remove
; "replace", `Quick, test_replace
]
Loading

0 comments on commit 3b1e85b

Please sign in to comment.