This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
15
unikernel/duniverse/ocaml-h1/lib_test/dune
Normal file
15
unikernel/duniverse/ocaml-h1/lib_test/dune
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
(tests
|
||||
(libraries h1 alcotest)
|
||||
(modules
|
||||
helpers
|
||||
test_client_connection
|
||||
test_headers
|
||||
test_h1
|
||||
test_iovec
|
||||
test_method
|
||||
test_request
|
||||
test_response
|
||||
test_server_connection
|
||||
test_version
|
||||
test_websocket)
|
||||
(names test_h1))
|
||||
76
unikernel/duniverse/ocaml-h1/lib_test/helpers.ml
Normal file
76
unikernel/duniverse/ocaml-h1/lib_test/helpers.ml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
open H1
|
||||
|
||||
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
|
||||
H1_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
|
||||
H1_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 | `Upgrade ]
|
||||
|
||||
let pp_hum fmt (t : t) =
|
||||
let str =
|
||||
match t with
|
||||
| `Read -> "Read"
|
||||
| `Yield -> "Yield"
|
||||
| `Close -> "Close"
|
||||
| `Upgrade -> "Upgrade"
|
||||
in
|
||||
Format.pp_print_string fmt str
|
||||
;;
|
||||
end
|
||||
|
||||
module Write_operation = struct
|
||||
type t = [ `Write of Bstr.t IOVec.t list | `Yield | `Close of int | `Upgrade ]
|
||||
|
||||
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 } ->
|
||||
Bstr.blit_to_bytes buffer ~src_off bytes ~dst_off:!dst_off ~len;
|
||||
dst_off := !dst_off + len)
|
||||
iovecs;
|
||||
Bytes.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
|
||||
| `Upgrade -> Format.pp_print_string fmt "Upgrade"
|
||||
;;
|
||||
|
||||
let to_write_as_string t =
|
||||
match t with
|
||||
| `Write iovecs -> Some (iovecs_to_string iovecs)
|
||||
| `Close _ | `Yield | `Upgrade -> None
|
||||
;;
|
||||
end
|
||||
|
||||
let write_operation = Alcotest.of_pp Write_operation.pp_hum
|
||||
let read_operation = Alcotest.of_pp Read_operation.pp_hum
|
||||
|
||||
module Headers = struct
|
||||
include Headers
|
||||
|
||||
let (@) a b = Headers.add_list a (Headers.to_list b)
|
||||
|
||||
let connection_close = Headers.of_list ["connection", "close"]
|
||||
let encoding_chunked = Headers.of_list ["transfer-encoding", "chunked"]
|
||||
let encoding_fixed n = Headers.of_list ["content-length", string_of_int n]
|
||||
let upgrade protocol = Headers.of_list ["connection", "upgrade" ; "upgrade", protocol]
|
||||
end
|
||||
339
unikernel/duniverse/ocaml-h1/lib_test/test_client_connection.ml
Normal file
339
unikernel/duniverse/ocaml-h1/lib_test/test_client_connection.ml
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
open H1
|
||||
open Helpers
|
||||
open Client_connection
|
||||
|
||||
let response_error_pp_hum fmt = function
|
||||
| `Malformed_response str ->
|
||||
Format.fprintf fmt "Malformed_response: %s" str
|
||||
| `Invalid_response_body_length resp ->
|
||||
Format.fprintf fmt "Invalid_response_body_length: %s" (response_to_string resp)
|
||||
| `Exn exn ->
|
||||
Format.fprintf fmt "Exn (%s)" (Printexc.to_string exn)
|
||||
;;
|
||||
|
||||
module Response = struct
|
||||
include Response
|
||||
|
||||
let pp = pp_hum
|
||||
let equal x y = x = y
|
||||
end
|
||||
|
||||
module Alcotest = struct
|
||||
include Alcotest
|
||||
|
||||
let response_error = of_pp response_error_pp_hum
|
||||
end
|
||||
|
||||
let feed_string t str =
|
||||
let len = String.length str in
|
||||
let input = Bstr.of_string str 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 response_string = response_to_string r in
|
||||
read_string t response_string
|
||||
;;
|
||||
|
||||
let reader_ready t =
|
||||
Alcotest.check read_operation "Reader is ready"
|
||||
`Read (next_read_operation t :> Read_operation.t);
|
||||
;;
|
||||
|
||||
let reader_closed t =
|
||||
Alcotest.check read_operation "Reader is closed"
|
||||
`Close (next_read_operation t :> Read_operation.t);
|
||||
;;
|
||||
|
||||
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 :> Write_operation.t);
|
||||
;;
|
||||
|
||||
let writer_closed t =
|
||||
Alcotest.check write_operation "Writer is closed"
|
||||
(`Close 0) (next_write_operation t :> Write_operation.t);
|
||||
;;
|
||||
|
||||
let connection_is_shutdown t =
|
||||
Alcotest.check read_operation "Reader is closed"
|
||||
`Close (next_read_operation t :> Read_operation.t);
|
||||
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.Reader.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.Writer.close body;
|
||||
write_request t request';
|
||||
writer_closed t;
|
||||
read_response t response;
|
||||
|
||||
(* Single GET, response closes connection *)
|
||||
let response = Response.create `OK ~headers:Headers.connection_close in
|
||||
let body, t =
|
||||
request
|
||||
request'
|
||||
~response_handler:(default_response_handler response)
|
||||
~error_handler:no_error_handler
|
||||
in
|
||||
Body.Writer.close body;
|
||||
write_request t request';
|
||||
read_response t response;
|
||||
let c = read_eof t Bstr.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.encoding_chunked in
|
||||
let body, t =
|
||||
request
|
||||
request'
|
||||
~response_handler:(default_response_handler response)
|
||||
~error_handler:no_error_handler
|
||||
in
|
||||
Body.Writer.close body;
|
||||
write_request t request';
|
||||
read_response t response;
|
||||
read_string t "d\r\nHello, world!\r\n0\r\n\r\n"
|
||||
;;
|
||||
|
||||
let test_send_streaming_body () =
|
||||
let request' = Request.create `GET "/" ~headers:Headers.encoding_chunked in
|
||||
let response = Response.create `OK ~headers:Headers.encoding_chunked in
|
||||
let body, t =
|
||||
request
|
||||
request'
|
||||
~response_handler:(default_response_handler response)
|
||||
~error_handler:no_error_handler
|
||||
in
|
||||
write_request t request';
|
||||
read_response t response;
|
||||
Body.Writer.write_string body "hello";
|
||||
write_string t "5\r\nhello\r\n";
|
||||
Body.Writer.write_string body "world";
|
||||
Body.Writer.close body;
|
||||
write_string t "5\r\nworld\r\n";
|
||||
write_string t "0\r\n\r\n";
|
||||
writer_closed t
|
||||
;;
|
||||
|
||||
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.Writer.close body;
|
||||
write_request t request';
|
||||
writer_closed t;
|
||||
reader_ready t;
|
||||
let c = read_eof t Bstr.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_response_header_order () =
|
||||
let request' = Request.create `GET "/" in
|
||||
let headers =
|
||||
[ "a", "1"
|
||||
; "b", "2"
|
||||
; "c", "3"
|
||||
]
|
||||
in
|
||||
let response = Response.create `OK ~headers:(Headers.of_list headers) in
|
||||
let received = ref None in
|
||||
let body, t =
|
||||
request
|
||||
request'
|
||||
~response_handler:(fun response _ -> received := Some response)
|
||||
~error_handler:no_error_handler
|
||||
in
|
||||
Body.Writer.close body;
|
||||
write_request t request';
|
||||
writer_closed t;
|
||||
read_response t response;
|
||||
match !received with
|
||||
| None -> assert false
|
||||
| Some received ->
|
||||
Alcotest.(check (list (pair string string))) "headers are equal"
|
||||
headers (Headers.to_list received.headers);
|
||||
;;
|
||||
|
||||
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.Writer.close 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.Writer.close 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 test_failed_response_parse () =
|
||||
let request' = Request.create `GET "/" in
|
||||
|
||||
let test response bytes_read expected_error =
|
||||
let error = ref None in
|
||||
let body, t =
|
||||
request
|
||||
request'
|
||||
~response_handler:(fun _ _ -> assert false)
|
||||
~error_handler:(fun e -> error := Some e)
|
||||
in
|
||||
Body.Writer.close body;
|
||||
write_request t request';
|
||||
writer_closed t;
|
||||
reader_ready t;
|
||||
let len = feed_string t response in
|
||||
Alcotest.(check int) "bytes read" len bytes_read;
|
||||
connection_is_shutdown t;
|
||||
Alcotest.(check (option response_error)) "Response error"
|
||||
(Some expected_error) !error;
|
||||
in
|
||||
|
||||
test "HTTP/1.1 200\r\n\r\n" 12 (`Malformed_response ": char ' '");
|
||||
|
||||
let response = Response.create `OK ~headers:(Headers.encoding_fixed (-1)) in
|
||||
test (response_to_string response) 39 (`Invalid_response_body_length response);
|
||||
;;
|
||||
|
||||
let test_schedule_read_with_data_available () =
|
||||
let request' = Request.create `GET "/" in
|
||||
let response = Response.create `OK ~headers:(Headers.encoding_fixed 6) in
|
||||
|
||||
let body = ref None in
|
||||
let response_handler response' body' =
|
||||
body := Some body';
|
||||
Alcotest.check (module Response) "expected response" response response';
|
||||
in
|
||||
let req_body, t =
|
||||
request request' ~response_handler ~error_handler:no_error_handler
|
||||
in
|
||||
Body.Writer.close req_body;
|
||||
write_request t request';
|
||||
writer_closed t;
|
||||
read_response t response;
|
||||
|
||||
let body = Option.get !body in
|
||||
let schedule_read expected =
|
||||
let did_read = ref false in
|
||||
Body.Reader.schedule_read body
|
||||
~on_read:(fun buf ~off ~len ->
|
||||
let actual = Bstr.sub_string buf ~off ~len in
|
||||
did_read := true;
|
||||
Alcotest.(check string) "Body" expected actual)
|
||||
~on_eof:(fun () -> assert false);
|
||||
Alcotest.(check bool) "on_read called" true !did_read;
|
||||
in
|
||||
|
||||
(* We get some data on the connection, but not the full response yet. *)
|
||||
read_string t "Hello";
|
||||
|
||||
(* Schedule a read when there is already data available. on_read should be called
|
||||
straight away, as who knows how long it'll be before more data arrives. *)
|
||||
schedule_read "Hello";
|
||||
read_string t "!";
|
||||
schedule_read "!";
|
||||
let did_eof = ref false in
|
||||
Body.Reader.schedule_read body
|
||||
~on_read:(fun _ ~off:_ ~len:_ -> Alcotest.fail "Expected eof")
|
||||
~on_eof:(fun () -> did_eof := true);
|
||||
Alcotest.(check bool) "on_eof called" true !did_eof;
|
||||
reader_closed t;
|
||||
;;
|
||||
|
||||
let tests =
|
||||
[ "GET" , `Quick, test_get
|
||||
; "send streaming body", `Quick, test_send_streaming_body
|
||||
; "Response EOF", `Quick, test_response_eof
|
||||
; "Response header order preserved", `Quick, test_response_header_order
|
||||
; "report_exn" , `Quick, test_report_exn
|
||||
; "input_shrunk", `Quick, test_input_shrunk
|
||||
; "failed response parse", `Quick, test_failed_response_parse
|
||||
; "schedule read with data available", `Quick, test_schedule_read_with_data_available
|
||||
]
|
||||
12
unikernel/duniverse/ocaml-h1/lib_test/test_h1.ml
Normal file
12
unikernel/duniverse/ocaml-h1/lib_test/test_h1.ml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
let () =
|
||||
Alcotest.run "h1 unit tests"
|
||||
[ "websocket" , Test_websocket.tests
|
||||
; "version" , Test_version.tests
|
||||
; "method" , Test_method.tests
|
||||
; "iovec" , Test_iovec.tests
|
||||
; "headers" , Test_headers.tests
|
||||
; "request" , Test_request.tests
|
||||
; "response" , Test_response.tests
|
||||
; "client connection", Test_client_connection.tests
|
||||
; "server connection", Test_server_connection.tests
|
||||
]
|
||||
78
unikernel/duniverse/ocaml-h1/lib_test/test_headers.ml
Normal file
78
unikernel/duniverse/ocaml-h1/lib_test/test_headers.ml
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
open H1
|
||||
module Array = ArrayLabels
|
||||
module List = ListLabels
|
||||
|
||||
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 test_ci_equal () =
|
||||
let string_of_char x = String.init 1 (fun _ -> x) in
|
||||
let ascii =
|
||||
Array.init (0xff + 1) ~f:Char.chr
|
||||
|> Array.to_list
|
||||
in
|
||||
let ascii_pairs =
|
||||
List.map ascii ~f:(fun x ->
|
||||
List.map ascii ~f:(fun y -> x, y))
|
||||
|> List.concat
|
||||
in
|
||||
(* Ensure that the branch free case-insensitive equality check is consistent
|
||||
* with a naive implementation. *)
|
||||
List.iter ascii_pairs ~f:(fun (x, y) ->
|
||||
let char_ci_equal =
|
||||
Char.compare (Char.lowercase_ascii x) (Char.lowercase_ascii y) = 0
|
||||
in
|
||||
let headers_equal =
|
||||
let headers = Headers.of_list [ string_of_char y, "value" ] in
|
||||
Headers.mem headers (string_of_char x)
|
||||
in
|
||||
Alcotest.(check bool)
|
||||
(Printf.sprintf "CI: %C = %C" x y)
|
||||
char_ci_equal
|
||||
headers_equal)
|
||||
;;
|
||||
|
||||
|
||||
|
||||
let tests =
|
||||
[ "remove" , `Quick, test_remove
|
||||
; "replace" , `Quick, test_replace
|
||||
; "CI equal", `Quick, test_ci_equal
|
||||
]
|
||||
43
unikernel/duniverse/ocaml-h1/lib_test/test_iovec.ml
Normal file
43
unikernel/duniverse/ocaml-h1/lib_test/test_iovec.ml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
open H1
|
||||
open IOVec
|
||||
|
||||
(* The length of the buffer is ignored by iovec operations *)
|
||||
let buffer = Bstr.empty
|
||||
|
||||
let test_lengthv () =
|
||||
Alcotest.(check int) "lengthv [] = 0" (lengthv []) 0;
|
||||
Alcotest.(check int) "lengthv [iovec] = length iovec"
|
||||
(lengthv [{ buffer; off = 0; len = 0 }]) (length {buffer; off = 0; len = 0 });
|
||||
Alcotest.(check int) "lengthv [iovec] = length iovec"
|
||||
(lengthv [{ buffer; off = 0; len = 10 }]) (length {buffer; off = 0; len = 10 });
|
||||
;;
|
||||
|
||||
let test_shiftv_raises () =
|
||||
Alcotest.check_raises
|
||||
"IOVec.shiftv: -1 is a negative number"
|
||||
(Failure "IOVec.shiftv: -1 is a negative number")
|
||||
(fun () -> ignore (shiftv [] (-1)));
|
||||
let test f =
|
||||
Alcotest.check_raises
|
||||
"shiftv iovecs n raises when n > lengthv iovecs"
|
||||
(Failure "shiftv: n > lengthv iovecs")
|
||||
(fun () -> ignore (f ()))
|
||||
in
|
||||
test (fun () -> shiftv [] 1);
|
||||
test (fun () -> shiftv [{ buffer; off = 0; len = 1 }] 2);
|
||||
test (fun () -> shiftv [{ buffer; off = 0; len = 1 }; { buffer; off = 0; len = 1 }] 3);
|
||||
;;
|
||||
|
||||
let test_shiftv () =
|
||||
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [] 0 = []" (shiftv [] 0) [];
|
||||
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [{... len ... }] len = []"
|
||||
(shiftv [{ buffer; off = 0; len = 1 }] 1) [];
|
||||
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [iovec] n when length iovec < n"
|
||||
(shiftv [{ buffer; off = 0; len = 4 }] 2) [{ buffer; off = 2; len = 2 }];
|
||||
;;
|
||||
|
||||
let tests =
|
||||
[ "lengthv" , `Quick, test_lengthv
|
||||
; "shiftv" , `Quick, test_shiftv
|
||||
; "shiftv raises ", `Quick, test_shiftv_raises
|
||||
]
|
||||
41
unikernel/duniverse/ocaml-h1/lib_test/test_method.ml
Normal file
41
unikernel/duniverse/ocaml-h1/lib_test/test_method.ml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
open H1
|
||||
open Method
|
||||
|
||||
let test_is_safe () =
|
||||
Alcotest.(check bool) "GET is safe" (is_safe `GET ) true;
|
||||
Alcotest.(check bool) "HEAD is safe" (is_safe `HEAD) true;
|
||||
Alcotest.(check bool) "POST is safe" (is_safe `POST) false;
|
||||
Alcotest.(check bool) "PUT is safe" (is_safe `PUT ) false;
|
||||
Alcotest.(check bool) "DELETE is safe" (is_safe `DELETE ) false;
|
||||
Alcotest.(check bool) "CONNECT is safe" (is_safe `CONNECT) false;
|
||||
Alcotest.(check bool) "OPTIONS is safe" (is_safe `OPTIONS) true;
|
||||
Alcotest.(check bool) "TRACE is safe" (is_safe `TRACE ) true;
|
||||
;;
|
||||
|
||||
let test_is_cacheable () =
|
||||
Alcotest.(check bool) "GET is cacheable" (is_cacheable `GET ) true;
|
||||
Alcotest.(check bool) "HEAD is cacheable" (is_cacheable `HEAD) true;
|
||||
Alcotest.(check bool) "POST is cacheable" (is_cacheable `POST) true;
|
||||
Alcotest.(check bool) "PUT is cacheable" (is_cacheable `PUT ) false;
|
||||
Alcotest.(check bool) "DELETE is cacheable" (is_cacheable `DELETE ) false;
|
||||
Alcotest.(check bool) "CONNECT is cacheable" (is_cacheable `CONNECT) false;
|
||||
Alcotest.(check bool) "OPTIONS is cacheable" (is_cacheable `OPTIONS) false;
|
||||
Alcotest.(check bool) "TRACE is cacheable" (is_cacheable `TRACE ) false;
|
||||
;;
|
||||
|
||||
let test_is_idempotent () =
|
||||
Alcotest.(check bool) "GET is idempotent" (is_idempotent `GET ) true;
|
||||
Alcotest.(check bool) "HEAD is idempotent" (is_idempotent `HEAD) true;
|
||||
Alcotest.(check bool) "POST is idempotent" (is_idempotent `POST) false;
|
||||
Alcotest.(check bool) "PUT is idempotent" (is_idempotent `PUT ) true;
|
||||
Alcotest.(check bool) "DELETE is idempotent" (is_idempotent `DELETE ) true;
|
||||
Alcotest.(check bool) "CONNECT is idempotent" (is_idempotent `CONNECT) false;
|
||||
Alcotest.(check bool) "OPTIONS is idempotent" (is_idempotent `OPTIONS) true;
|
||||
Alcotest.(check bool) "TRACE is idempotent" (is_idempotent `TRACE ) true;
|
||||
;;
|
||||
|
||||
let tests =
|
||||
[ "is_safe" , `Quick, test_is_safe
|
||||
; "is_cacheable" , `Quick, test_is_cacheable
|
||||
; "is_idempotent", `Quick, test_is_idempotent
|
||||
]
|
||||
104
unikernel/duniverse/ocaml-h1/lib_test/test_request.ml
Normal file
104
unikernel/duniverse/ocaml-h1/lib_test/test_request.ml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
open H1
|
||||
open Request
|
||||
open Helpers
|
||||
|
||||
let body_length = Alcotest.of_pp Request.Body_length.pp_hum
|
||||
|
||||
let check =
|
||||
let alco =
|
||||
Alcotest.result
|
||||
(Alcotest.of_pp pp_hum)
|
||||
Alcotest.string
|
||||
in
|
||||
fun message ~expect input ->
|
||||
let actual =
|
||||
Angstrom.parse_string ~consume:All H1_private.Parse.request input
|
||||
in
|
||||
Alcotest.check alco message expect actual
|
||||
;;
|
||||
|
||||
let test_parse_valid () =
|
||||
check
|
||||
"valid GET without headers"
|
||||
~expect:(Ok (Request.create `GET "/"))
|
||||
"GET / HTTP/1.1\r\n\r\n";
|
||||
check
|
||||
"valid non-standard method without headers"
|
||||
~expect:(Ok (Request.create (`Other "some-other-verb") "/"))
|
||||
"some-other-verb / HTTP/1.1\r\n\r\n";
|
||||
check
|
||||
"valid GET with headers"
|
||||
~expect:(Ok (Request.create ~headers:(Headers.of_list [ "Link", "/path/to/some/website"]) `GET "/"))
|
||||
"GET / HTTP/1.1\r\nLink: /path/to/some/website\r\n\r\n";
|
||||
;;
|
||||
|
||||
let test_parse_invalid_errors () =
|
||||
check
|
||||
"doesn't end"
|
||||
~expect:(Error ": not enough input")
|
||||
"GET / HTTP/1.1\r\n";
|
||||
check
|
||||
"invalid version"
|
||||
~expect:(Error "eol: string")
|
||||
"GET / HTTP/1.22\r\n\r\n";
|
||||
check
|
||||
"malformed header"
|
||||
~expect:(Error "header: char ':'")
|
||||
"GET / HTTP/1.1\r\nLink : /path/to/some/website\r\n\r\n";
|
||||
;;
|
||||
|
||||
let test_body_length () =
|
||||
let check message request ~expect =
|
||||
let actual = Request.body_length request in
|
||||
Alcotest.check body_length message expect actual
|
||||
in
|
||||
let req method_ headers = Request.create method_ ~headers "/" in
|
||||
check
|
||||
"no headers"
|
||||
~expect:(`Fixed 0L)
|
||||
(req `GET Headers.empty);
|
||||
check
|
||||
"single fixed"
|
||||
~expect:(`Fixed 10L)
|
||||
(req `GET Headers.(encoding_fixed 10));
|
||||
check
|
||||
"negative fixed"
|
||||
~expect:(`Error `Bad_request)
|
||||
(req `GET Headers.(encoding_fixed (-10)));
|
||||
check
|
||||
"multiple fixed"
|
||||
~expect:(`Error `Bad_request)
|
||||
(req `GET Headers.(encoding_fixed 10 @ encoding_fixed 20));
|
||||
check
|
||||
"chunked"
|
||||
~expect:`Chunked
|
||||
(req `GET Headers.encoding_chunked);
|
||||
check
|
||||
"chunked multiple times"
|
||||
~expect:`Chunked
|
||||
(req `GET Headers.(encoding_chunked @ encoding_chunked));
|
||||
let encoding_gzip = Headers.of_list ["transfer-encoding", "gzip"] in
|
||||
check
|
||||
"non-chunked transfer-encoding"
|
||||
~expect:(`Error `Bad_request)
|
||||
(req `GET encoding_gzip);
|
||||
check
|
||||
"chunked after non-chunked"
|
||||
~expect:`Chunked
|
||||
(req `GET Headers.(encoding_gzip @ encoding_chunked));
|
||||
check
|
||||
"chunked before non-chunked"
|
||||
~expect:(`Error `Bad_request)
|
||||
(req `GET Headers.(encoding_chunked @ encoding_gzip));
|
||||
check
|
||||
"chunked case-insensitive"
|
||||
~expect:`Chunked
|
||||
(req `GET Headers.(of_list ["transfer-encoding", "CHUNKED"]));
|
||||
;;
|
||||
|
||||
|
||||
let tests =
|
||||
[ "parse valid" , `Quick, test_parse_valid
|
||||
; "parse invalid errors", `Quick, test_parse_invalid_errors
|
||||
; "body length", `Quick, test_body_length
|
||||
]
|
||||
115
unikernel/duniverse/ocaml-h1/lib_test/test_response.ml
Normal file
115
unikernel/duniverse/ocaml-h1/lib_test/test_response.ml
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
open H1
|
||||
open Response
|
||||
open Helpers
|
||||
|
||||
let body_length = Alcotest.of_pp Response.Body_length.pp_hum
|
||||
|
||||
let check =
|
||||
let alco =
|
||||
Alcotest.result
|
||||
(Alcotest.of_pp pp_hum)
|
||||
Alcotest.string
|
||||
in
|
||||
fun message ~expect input ->
|
||||
let actual =
|
||||
Angstrom.parse_string ~consume:All H1_private.Parse.response input
|
||||
in
|
||||
Alcotest.check alco message expect actual
|
||||
;;
|
||||
|
||||
let test_parse_valid () =
|
||||
check
|
||||
"OK response without headers"
|
||||
~expect:(Ok (Response.create `OK))
|
||||
"HTTP/1.1 200 OK\r\n\r\n";
|
||||
;;
|
||||
|
||||
let test_parse_invalid_error () =
|
||||
check
|
||||
"OK response without a status message"
|
||||
~expect:(Error ": char ' '")
|
||||
"HTTP/1.1 200\r\n\r\n";
|
||||
check
|
||||
"OK response without a status message"
|
||||
~expect:(Error ": status-code empty")
|
||||
"HTTP/1.1 OK\r\n\r\n";
|
||||
check
|
||||
"OK response without a status message"
|
||||
~expect:(Error ": status-code too long: \"999999937377999999999200\"")
|
||||
"HTTP/1.1 999999937377999999999200\r\n\r\n";
|
||||
;;
|
||||
|
||||
let test_body_length () =
|
||||
let check message request_method response ~expect =
|
||||
let actual = Response.body_length response ~request_method in
|
||||
Alcotest.check body_length message expect actual
|
||||
in
|
||||
let res status headers = Response.create status ~headers in
|
||||
check
|
||||
"requested HEAD"
|
||||
~expect:(`Fixed 0L)
|
||||
`HEAD (res `OK Headers.empty);
|
||||
check
|
||||
"requested CONNECT"
|
||||
~expect:(`Close_delimited)
|
||||
`CONNECT (res `OK Headers.empty);
|
||||
check
|
||||
"status: informational"
|
||||
~expect:(`Fixed 0L)
|
||||
`GET (res `Continue Headers.empty);
|
||||
check
|
||||
"status: no content"
|
||||
~expect:(`Fixed 0L)
|
||||
`GET (res `No_content Headers.empty);
|
||||
check
|
||||
"status: not modified"
|
||||
~expect:(`Fixed 0L)
|
||||
`GET (res `Not_modified Headers.empty);
|
||||
check
|
||||
"no header"
|
||||
~expect:(`Close_delimited)
|
||||
`GET (res `OK Headers.empty);
|
||||
check
|
||||
"single fixed"
|
||||
~expect:(`Fixed 10L)
|
||||
`GET (res `OK Headers.(encoding_fixed 10));
|
||||
check
|
||||
"negative fixed"
|
||||
~expect:(`Error `Internal_server_error)
|
||||
`GET (res `OK Headers.(encoding_fixed (-10)));
|
||||
check
|
||||
"multiple fixed"
|
||||
~expect:(`Error `Internal_server_error)
|
||||
`GET (res `OK Headers.(encoding_fixed 10 @ encoding_fixed 20));
|
||||
check
|
||||
"chunked"
|
||||
~expect:`Chunked
|
||||
`GET (res `OK Headers.encoding_chunked);
|
||||
check
|
||||
"chunked multiple times"
|
||||
~expect:`Chunked
|
||||
`GET (res `OK Headers.(encoding_chunked @ encoding_chunked));
|
||||
let encoding_gzip = Headers.of_list ["transfer-encoding", "gzip"] in
|
||||
check
|
||||
"non-chunked transfer-encoding"
|
||||
~expect:`Close_delimited
|
||||
`GET (res `OK encoding_gzip);
|
||||
check
|
||||
"chunked after non-chunked"
|
||||
~expect:`Chunked
|
||||
`GET (res `OK Headers.(encoding_gzip @ encoding_chunked));
|
||||
check
|
||||
"chunked before non-chunked"
|
||||
~expect:`Close_delimited
|
||||
`GET (res `OK Headers.(encoding_chunked @ encoding_gzip));
|
||||
check
|
||||
"chunked case-insensitive"
|
||||
~expect:`Chunked
|
||||
`GET (res `OK Headers.(of_list ["transfer-encoding", "CHUNKED"]));
|
||||
;;
|
||||
|
||||
let tests =
|
||||
[ "parse valid" , `Quick, test_parse_valid
|
||||
; "parse invalid error", `Quick, test_parse_invalid_error
|
||||
; "body length" , `Quick, test_body_length
|
||||
]
|
||||
1306
unikernel/duniverse/ocaml-h1/lib_test/test_server_connection.ml
Normal file
1306
unikernel/duniverse/ocaml-h1/lib_test/test_server_connection.ml
Normal file
File diff suppressed because it is too large
Load diff
22
unikernel/duniverse/ocaml-h1/lib_test/test_version.ml
Normal file
22
unikernel/duniverse/ocaml-h1/lib_test/test_version.ml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
open H1
|
||||
open Version
|
||||
|
||||
let v1_0 = { major = 1; minor = 0 }
|
||||
let v1_1 = { major = 1; minor = 1 }
|
||||
|
||||
let test_compare () =
|
||||
Alcotest.(check int) "compare v1_1 v1_0" (compare v1_1 v1_0) 1;
|
||||
Alcotest.(check int) "compare v1_1 v1_1" (compare v1_1 v1_1) 0;
|
||||
Alcotest.(check int) "compare v1_0 v1_0" (compare v1_0 v1_0) 0;
|
||||
Alcotest.(check int) "compare v1_0 v1_1" (compare v1_0 v1_1) (-1);
|
||||
;;
|
||||
|
||||
let test_to_string () =
|
||||
Alcotest.(check string) "to_string v1_1" (to_string v1_1) "HTTP/1.1";
|
||||
Alcotest.(check string) "to_string v1_0" (to_string v1_0) "HTTP/1.0";
|
||||
;;
|
||||
|
||||
let tests =
|
||||
[ "compare" , `Quick, test_compare
|
||||
; "to_string", `Quick, test_to_string
|
||||
]
|
||||
46
unikernel/duniverse/ocaml-h1/lib_test/test_websocket.ml
Normal file
46
unikernel/duniverse/ocaml-h1/lib_test/test_websocket.ml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
open H1.Websocket
|
||||
|
||||
module Testable = struct
|
||||
let opcode = Alcotest.testable Opcode.pp_hum ( = )
|
||||
end
|
||||
|
||||
let parse_frame serialized_frame =
|
||||
match Angstrom.parse_string ~consume:All Frame.parse serialized_frame with
|
||||
| Ok frame -> frame
|
||||
| Error err -> Alcotest.fail err
|
||||
|
||||
let test_parsing_ping_frame () =
|
||||
let frame = parse_frame "\137\128\000\000\046\216" in
|
||||
Alcotest.check Testable.opcode "opcode" `Ping (Frame.opcode frame);
|
||||
Alcotest.(check bool) "has mask" true (Frame.has_mask frame);
|
||||
Alcotest.(check int32) "mask" 11992l (Frame.mask_exn frame);
|
||||
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 0;
|
||||
Alcotest.(check int) "length" (Frame.length frame) 6
|
||||
|
||||
let test_parsing_close_frame () =
|
||||
let frame = parse_frame "\136\000" in
|
||||
Alcotest.check Testable.opcode "opcode" `Connection_close
|
||||
(Frame.opcode frame);
|
||||
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 0;
|
||||
Alcotest.(check int) "length" (Frame.length frame) 2
|
||||
|
||||
let test_parsing_text_frame () =
|
||||
let frame =
|
||||
parse_frame
|
||||
"\129\139\086\057\046\216\103\011\029\236\099\015\025\224\111\009\036"
|
||||
in
|
||||
Alcotest.check Testable.opcode "opcode" `Text (Frame.opcode frame);
|
||||
Alcotest.(check bool) "has mask" true (Frame.has_mask frame);
|
||||
Alcotest.(check int32) "mask" 1446588120l (Frame.mask_exn frame);
|
||||
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 11;
|
||||
Alcotest.(check int) "length" (Frame.length frame) 17;
|
||||
Frame.unmask_inplace frame;
|
||||
let payload = Bstr.to_string (Frame.payload_view frame) in
|
||||
Alcotest.(check string) "payload" "1234567890\n" payload
|
||||
|
||||
let tests =
|
||||
[
|
||||
("parsing ping frame", `Quick, test_parsing_ping_frame);
|
||||
("parsing close frame", `Quick, test_parsing_close_frame);
|
||||
("parsing text frame", `Quick, test_parsing_text_frame);
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue