Skip to content

Commit

Permalink
Add browser-based alcotest tests
Browse files Browse the repository at this point in the history
  • Loading branch information
patricoferris committed Jan 14, 2023
1 parent 1654f56 commit 26f14f7
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 14 deletions.
4 changes: 3 additions & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
(description "An eio implementation that is compatible with programs running in the browser.")
(depends
(eio (= :version))
(brr (>= 0.0.4))))
(brr (>= 0.0.4))
(alcotest (and (>= 1.6.0) :with-test))
(ansi :with-test)))
(package
(name eio_main)
(synopsis "Effect-based direct-style IO mainloop for OCaml")
Expand Down
13 changes: 13 additions & 0 deletions lib_eio_js/browser/example/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
; We compile by hand because dune is a little broken w.r.t to
; separate compilation, js_of_ocaml and effects.
(executable
(name index)
(modes js)
(js_of_ocaml (flags --target-env=nodejs --enable=effects --debug-info --source-map --pretty))
(libraries eio_browser))

(rule
(alias default)
(deps index.html index.bc.js)
(targets index.js)
(action (copy index.bc.js index.js)))
15 changes: 15 additions & 0 deletions lib_eio_js/browser/example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eio in the Browser</title>
</head>
<body>
<div id="counter"></div>
<textarea name="text" id="text" cols="30" rows="10"></textarea>
<div id="output"></div>
<script src="index.js"></script>
</body>
</html>
File renamed without changes.
20 changes: 12 additions & 8 deletions lib_eio_js/browser/test/dune
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
; We compile by hand because dune is a little broken w.r.t to
; separate compilation, js_of_ocaml and effects.
(library
(name eio_test)
(modules eio_test)
(libraries eio alcotest))

(executable
(name index)
(name test)
(modules test)
(modes js)
(js_of_ocaml (flags --target-env=nodejs --enable=effects --debug-info --source-map --pretty))
(libraries eio_browser))
(js_of_ocaml (flags --target-env=nodejs --enable=effects --setenv=ALCOTEST_COLOR=always))
(libraries alcotest ansi eio_test eio_browser))

(rule
(alias default)
(deps index.html index.bc.js)
(targets index.js)
(action (copy index.bc.js index.js)))
(deps index.html test.bc.js)
(targets test.js)
(action (copy test.bc.js test.js)))
156 changes: 156 additions & 0 deletions lib_eio_js/browser/test/eio_test.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
open Eio

(* Filesystem tests *)
module Fs = struct
let ( / ) = Path.(/)
let test_read_write env () =
let test_file = "test.txt" in
let text = "abcdefgh" in
let len = String.length text in
let _write =
Path.with_open_out ~create:(`If_missing 0o644) (env#fs / test_file) @@ fun sink ->
Flow.copy_string text sink
in
let buf = Cstruct.create len in
let _read =
Path.with_open_in (env#fs / test_file) @@ fun source ->
Flow.read_exact source buf
in
Path.unlink (env#fs / test_file);
Alcotest.(check string) "same string" text (Cstruct.to_string buf)

let test_append env () =
let test_file = "test.txt" in
let text = "abcdefgh" in
let len = String.length text in
let _write =
Path.with_open_out ~create:(`If_missing 0o644) (env#fs / test_file) @@ fun sink ->
Flow.copy_string text sink
in
let _write2 =
Path.with_open_out ~append:true ~create:(`If_missing 0o644) (env#fs / test_file) @@ fun sink ->
Flow.copy_string text sink
in
let buf = Cstruct.create (2 * len) in
let _read =
Path.with_open_in (env#fs / test_file) @@ fun source ->
Flow.read_exact source buf
in
Path.unlink (env#fs / test_file);
Alcotest.(check string) "same string" (text ^ text) (Cstruct.to_string buf)

let optint_int63 = Alcotest.testable Optint.Int63.pp Optint.Int63.equal

let test_fails_if_exists env () =
let test_file = "./test.txt" in
let text = "abcdefgh" in
let _write =
Path.with_open_out ~create:(`If_missing 0o644) (env#fs / test_file) @@ fun sink ->
Flow.copy_string text sink
in
Fun.protect
(fun () ->
try
Path.with_open_out ~create:(`Exclusive 0o644) (env#fs / test_file) @@ fun _ ->
Alcotest.failf "Expected to fail with already exists"
with
| Eio.Exn.Io (Eio.Fs.E (Fs.Already_exists _), _) -> ()
| exn -> Alcotest.failf "Expected already exists exception, got: %a" Fmt.exn exn
) ~finally:(fun () -> Path.unlink (env#fs / test_file))

let test_read_write env () =
let test_file = "test.txt" in
let text = "abcdefgh" in
let len = String.length text in
let _write =
Path.with_open_out ~create:(`If_missing 0o644) (env#fs / test_file) @@ fun sink ->
Flow.copy_string text sink
in
let stat =
Eio.Switch.run @@ fun sw ->
let fd = Path.open_in ~sw (env#fs / test_file) in
File.stat fd
in
let is_file = stat.kind = `Regular_file in
Alcotest.(check bool) "is file" true is_file;
Alcotest.(check optint_int63) "same size" (Optint.Int63.of_int len) stat.size;
Path.unlink (env#fs / test_file)

let tests env = [
Alcotest.test_case "read and write" `Quick (test_read_write env);
Alcotest.test_case "append" `Quick (test_append env);
Alcotest.test_case "fail if exists" `Quick (test_fails_if_exists env);
Alcotest.test_case "fstat" `Quick (test_fails_if_exists env);
]
end

module Fibers = struct
let test_yield () =
let expect = [1; 2] in
let res = ref [] in
Fiber.both
(fun () -> Fiber.yield (); res := 1 :: !res)
(fun () -> res := 2 :: !res);
Alcotest.(check (list int)) "same list" expect !res

let test_simple_cancel () =
let res =
Fiber.first
(fun () -> "a")
(fun () -> Fiber.yield (); failwith "b crashed")
in
Alcotest.(check string) "same string" "a" res;
let p, _r = Promise.create () in
let res = Fiber.first
(fun () -> "a")
(fun () -> Promise.await p)
in
Alcotest.(check string) "promise cancelled" "a" res


let tests = [
Alcotest.test_case "yielding" `Quick test_yield;
Alcotest.test_case "fiber first cancel" `Quick test_simple_cancel;
]
end

module Stream = struct
type op = [ `Add of int | `Take of int ]

let pp_op ppf = function
| `Add i -> Fmt.pf ppf "add %i" i
| `Take i -> Fmt.pf ppf "take %i" i

let op = Alcotest.of_pp pp_op

let test_stream () =
let l = ref [] in
let add s v =
Stream.add s v;
l := (`Add v) :: !l
in
let take s =
l := (`Take (Stream.take s)) :: !l
in
let t = Stream.create 3 in
add t 1;
Fiber.both
(fun () ->
add t 2;
add t 3;
add t 4;
)
(fun () ->
take t;
take t;
take t;
take t
);
let actual = List.rev !l in
let expected = [ `Add 1; `Add 2; `Add 3; `Take 1; `Take 2; `Take 3; `Take 4; `Add 4 ] in
Alcotest.(check (list op)) "same sequence" expected actual

let tests = [
Alcotest.test_case "stream1" `Quick test_stream
]
end
13 changes: 8 additions & 5 deletions lib_eio_js/browser/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eio in the Browser</title>
<title>Eio Browser Tests</title>
<style>
html, body {
margin: 0;
background-color: #131212;
}
</style>
</head>
<body>
<div id="counter"></div>
<textarea name="text" id="text" cols="30" rows="10"></textarea>
<div id="output"></div>
<script src="index.js"></script>
<script src="test.js"></script>
</body>
</html>
86 changes: 86 additions & 0 deletions lib_eio_js/browser/test/test.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
(* Avoiding dependency on js_of_ocaml *)
external set_channel_output' :
out_channel -> Jv.t -> unit
= "caml_ml_set_channel_output"

let set_channel_flusher (out_channel : out_channel) (f : string -> unit) =
let f' =
Jv.callback ~arity:1 (fun s -> f (Jstr.binary_to_octets s))
in
set_channel_output' out_channel f'

let init () =
let open Brr in
let parser = Ansi.create () in
let output =
let out = El.div [ El.h1 [ El.txt' "Eio_browser" ] ] in
El.set_at (Jstr.v "style")
(Some
(Jstr.v
"font-family: monospace; color: #e8e8e8; background: #131212; \
padding: 2em;"))
out;
El.(set_prop Prop.id (Jstr.v "output") out);
let style = El.style [ El.txt' Ansi.css ] in
El.append_children (Document.body G.document) [ style; out ];
out
in
let get_or_make name =
match Document.find_el_by_id G.document (Jstr.v name) with
| Some v -> v
| None ->
let d = El.div [] in
El.append_children output [ d ];
El.(set_prop Prop.id (Jstr.v name) d);
d
in
let append name s =
Brr.Console.log [ s ];
let s = Ansi.process parser s in
let p = El.pre [] in
El.to_jv p |> fun jv ->
Jv.set jv "innerHTML" (Jv.of_string s);
El.append_children (get_or_make name) [ p ]
in
set_channel_flusher stdout (fun content -> append "stdout" content);
set_channel_flusher stderr (fun content -> append "stderr" content);
()

module Browser_tests = struct
open Eio

let test_timeout_cancel () =
let v =
Fiber.first
(fun () -> Eio_browser.Timeout.sleep ~ms:5000; "A")
(fun () -> "B")
in
Alcotest.(check string) "timeout cancelled" "B" v

let test_fut_cancel () =
let p, _ = Fut.create () in
let v =
Fiber.first
(fun () -> Eio_browser.await p; "A")
(fun () -> "B")
in
Alcotest.(check string) "fut cancelled" "B" v

let tests = [
Alcotest.test_case "timeout cancelled" `Quick test_timeout_cancel;
Alcotest.test_case "fut cancelled" `Quick test_fut_cancel
]
end


let () =
init ();
let main =
Eio_browser.run @@ fun env ->
try Alcotest.run "eio" [
"fibers", Eio_test.Fibers.tests;
"stream", Eio_test.Stream.tests;
"browser", Browser_tests.tests
] with Exit -> ()
in
Fut.await main (fun () -> ())

0 comments on commit 26f14f7

Please sign in to comment.