Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions lib_eio/mock/dir.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
open Eio.Std

type ty = [ Eio.Fs.dir_ty | `Close | `Mock ]
type t = ty r

type state = {
label : string;
on_open_in : Eio.File.ro_ty r Handler.t;
on_open_out : Eio.File.rw_ty r Handler.t;
on_open_subtree : [`Close | Eio.Fs.dir_ty] r Handler.t;
on_read_dir : string list Handler.t;
on_dir_entries : (Eio.File.Stat.kind * string) list Handler.t;
on_stat : Eio.File.Stat.t Handler.t;
on_read_link : string Handler.t;
on_mkdir : unit Handler.t;
on_unlink : unit Handler.t;
on_rmdir : unit Handler.t;
on_rename : unit Handler.t;
on_symlink : unit Handler.t;
on_chmod : unit Handler.t;
on_chown : unit Handler.t;
}

let make_state label =
let on_read_dir = Handler.make (`Raise (Failure "Mock read_dir handler not configured")) in
(* By default, report the [on_read_dir] entries with an unknown kind. *)
let on_dir_entries =
Handler.make (`Run (fun () ->
Handler.run on_read_dir |> List.map (fun name -> (`Unknown, name))
))
in
{
label;
on_open_in = Handler.make (`Raise (Failure "Mock open_in handler not configured"));
on_open_out = Handler.make (`Raise (Failure "Mock open_out handler not configured"));
on_open_subtree = Handler.make (`Raise (Failure "Mock open_subtree handler not configured"));
on_read_dir;
on_dir_entries;
on_stat = Handler.make (`Raise (Failure "Mock stat handler not configured"));
on_read_link = Handler.make (`Raise (Failure "Mock read_link handler not configured"));
on_mkdir = Handler.make (`Return ());
on_unlink = Handler.make (`Return ());
on_rmdir = Handler.make (`Return ());
on_rename = Handler.make (`Return ());
on_symlink = Handler.make (`Return ());
on_chmod = Handler.make (`Return ());
on_chown = Handler.make (`Return ());
}

let pp_create f = function
| `Never -> Fmt.string f "`Never"
| `If_missing perm -> Fmt.pf f "`If_missing 0o%o" perm
| `Or_truncate perm -> Fmt.pf f "`Or_truncate 0o%o" perm
| `Exclusive perm -> Fmt.pf f "`Exclusive 0o%o" perm

let pp_id = Fmt.option ~none:(Fmt.any "None") (fun f x -> Fmt.pf f "%Ld" x)

let close t = traceln "%s: closed" t.label

module Impl (Path_syntax : Eio.Fs.Pi.PATH) = struct
type t = state

let open_in t ~sw path =
traceln "%s: open_in %S" t.label path;
let f = Handler.run t.on_open_in in
Switch.on_release sw (fun () -> Eio.Resource.close f);
f

let open_out t ~sw ~append ~create path =
traceln "%s: open_out ~append:%b ~create:%a %S" t.label append pp_create create path;
let f = Handler.run t.on_open_out in
Switch.on_release sw (fun () -> Eio.Resource.close f);
f

let open_subtree t ~sw path =
traceln "%s: open_subtree %S" t.label path;
let d = Handler.run t.on_open_subtree in
Switch.on_release sw (fun () -> Eio.Resource.close d);
d

let mkdir t ~perm path =
traceln "%s: mkdir ~perm:0o%o %S" t.label perm path;
Handler.run t.on_mkdir

let read_dir t path =
traceln "%s: read_dir %S" t.label path;
Handler.run t.on_read_dir

let with_dir_entries t path fn =
traceln "%s: with_dir_entries %S" t.label path;
Handler.run t.on_dir_entries
|> List.to_seq
|> fn

let stat t ~follow path =
traceln "%s: stat ~follow:%b %S" t.label follow path;
Handler.run t.on_stat

let unlink t path =
traceln "%s: unlink %S" t.label path;
Handler.run t.on_unlink

let rmdir t path =
traceln "%s: rmdir %S" t.label path;
Handler.run t.on_rmdir

let rename t old_path new_dir new_path =
traceln "%s: rename %S to %a" t.label old_path Eio.Path.pp (new_dir, new_path);
Handler.run t.on_rename

let read_link t path =
traceln "%s: read_link %S" t.label path;
Handler.run t.on_read_link

let symlink ~link_to t path =
traceln "%s: symlink ~link_to:%S %S" t.label link_to path;
Handler.run t.on_symlink

let chmod t ~follow ~perm path =
traceln "%s: chmod ~follow:%b ~perm:0o%o %S" t.label follow perm path;
Handler.run t.on_chmod

let chown ~follow ?uid ?gid t path =
traceln "%s: chown ~follow:%b ~uid:%a ~gid:%a %S" t.label follow pp_id uid pp_id gid path;
Handler.run t.on_chown

let pp f t = Fmt.string f (String.escaped t.label)

let native _ _ = None

include Path_syntax
end

module Posix_impl = Impl (Eio_utils.Posix_path)
module Nt_impl = Impl (Eio_utils.Nt_path)

type (_, _, _) Eio.Resource.pi += Raw : ('t, 't -> state, ty) Eio.Resource.pi
let raw (Eio.Resource.T (t, ops)) = Eio.Resource.get ops Raw t

let make_handler dir : (state, ty) Eio.Resource.handler =
Eio.Resource.handler [
H (Eio.Fs.Pi.Dir, dir);
H (Eio.Resource.Close, close);
H (Raw, Fun.id);
]

let posix_handler = make_handler (module Posix_impl : Eio.Fs.Pi.DIR with type t = state)
let nt_handler = make_handler (module Nt_impl : Eio.Fs.Pi.DIR with type t = state)

let make ?(syntax = `Posix) label : t =
let handler =
match syntax with
| `Posix -> posix_handler
| `Windows -> nt_handler
in
Eio.Resource.T (make_state label, handler)

let on_open_in (t:t) actions =
let t = raw t in
let as_file x = (x :> Eio.File.ro_ty r) in
Handler.seq t.on_open_in (List.map (Action.map as_file) actions)

let on_open_out (t:t) actions =
let t = raw t in
let as_file x = (x :> Eio.File.rw_ty r) in
Handler.seq t.on_open_out (List.map (Action.map as_file) actions)

let on_open_subtree (t:t) actions =
let t = raw t in
let as_dir x = (x :> [`Close | Eio.Fs.dir_ty] r) in
Handler.seq t.on_open_subtree (List.map (Action.map as_dir) actions)

let on_read_dir (t:t) actions = Handler.seq (raw t).on_read_dir actions
let on_dir_entries (t:t) actions = Handler.seq (raw t).on_dir_entries actions
let on_stat (t:t) actions = Handler.seq (raw t).on_stat actions
let on_read_link (t:t) actions = Handler.seq (raw t).on_read_link actions
let on_mkdir (t:t) actions = Handler.seq (raw t).on_mkdir actions
let on_unlink (t:t) actions = Handler.seq (raw t).on_unlink actions
let on_rmdir (t:t) actions = Handler.seq (raw t).on_rmdir actions
let on_rename (t:t) actions = Handler.seq (raw t).on_rename actions
let on_symlink (t:t) actions = Handler.seq (raw t).on_symlink actions
let on_chmod (t:t) actions = Handler.seq (raw t).on_chmod actions
let on_chown (t:t) actions = Handler.seq (raw t).on_chown actions
1 change: 1 addition & 0 deletions lib_eio/mock/eio_mock.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Action = Action
module Handler = Handler
module Flow = Flow
module Net = Net
module Dir = Dir
module Clock = Clock
module Domain_manager = Domain_manager
module Backend = Backend
Expand Down
63 changes: 63 additions & 0 deletions lib_eio/mock/eio_mock.mli
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,69 @@ module Net : sig
(** [on_accept socket actions] configures how to respond when the server calls "accept". *)
end

(** Mock {!Eio.Fs} directories. *)
module Dir : sig
type ty = [ Eio.Fs.dir_ty | `Close | `Mock ]
type t = ty r

val make : ?syntax:[`Posix | `Windows] -> string -> t
(** [make label] is a new mock directory.
@param syntax The path syntax to use when joining and splitting paths (default [`Posix]). *)

val on_open_in : t -> _ Eio.File.ro Handler.actions -> unit
(** [on_open_in t actions] configures what to return when opening a file for reading. *)

val on_open_out : t -> _ Eio.File.rw Handler.actions -> unit
(** [on_open_out t actions] configures what to return when opening a file for writing. *)

val on_open_subtree : t -> [> `Close | Eio.Fs.dir_ty] r Handler.actions -> unit
(** [on_open_subtree t actions] configures what to return when opening a sub-directory.
Typically the actions return further mock directories. *)

val on_read_dir : t -> string list Handler.actions -> unit
(** [on_read_dir t actions] configures the entries to report when listing a directory.
This is used by {!Eio.Path.read_dir} and by default also by {!on_dir_entries}. *)

val on_dir_entries : t -> (Eio.File.Stat.kind * string) list Handler.actions -> unit
(** [on_dir_entries t actions] configures the entries to report from directory
list operations. By defaul, this runs the {!on_read_dir} handler and reports
every entry's kind as [`Unknown]. *)

val on_stat : t -> Eio.File.Stat.t Handler.actions -> unit
(** [on_stat t actions] configures the results of {!Eio.Path.stat}. *)

val on_read_link : t -> string Handler.actions -> unit
(** [on_read_link t actions] configures the results of {!Eio.Path.read_link}. *)

val on_mkdir : t -> unit Handler.actions -> unit
(** [on_mkdir t actions] configures what to do when {!Eio.Path.mkdir} is called.
By default it just returns unit; use this to simulate faults. *)

val on_unlink : t -> unit Handler.actions -> unit
(** [on_unlink t actions] configures what to do when {!Eio.Path.unlink} is called.
By default it just returns unit; use this to simulate faults. *)

val on_rmdir : t -> unit Handler.actions -> unit
(** [on_rmdir t actions] configures what to do when {!Eio.Path.rmdir} is called.
By default it just returns unit; use this to simulate faults. *)

val on_rename : t -> unit Handler.actions -> unit
(** [on_rename t actions] configures what to do when {!Eio.Path.rename} is called.
By default it just returns unit; use this to simulate faults. *)

val on_symlink : t -> unit Handler.actions -> unit
(** [on_symlink t actions] configures what to do when {!Eio.Path.symlink} is called.
By default it just returns unit; use this to simulate faults. *)

val on_chmod : t -> unit Handler.actions -> unit
(** [on_chmod t actions] configures what to do when {!Eio.Path.chmod} is called.
By default it just returns unit; use this to simulate faults. *)

val on_chown : t -> unit Handler.actions -> unit
(** [on_chown t actions] configures what to do when {!Eio.Path.chown} is called.
By default it just returns unit; use this to simulate faults. *)
end

(** A mock {!Eio.Time} clock for testing timeouts. *)
module Clock = Clock

Expand Down
Loading