(*---------------------------------------------------------------------------
   Copyright (c) 2015 The astring programmers. All rights reserved.
   Distributed under the ISC license, see terms at the end of the file.
  ---------------------------------------------------------------------------*)

open Testing
open Astring

let pp_pair ppf (a, b) =
  Format.fprintf ppf "@[<1>(%a,%a)@]" String.dump a String.dump b

let misc = test "Misc. base functions" @@ fun () ->
  eq_str String.empty  "";
  app_invalid ~pp:pp_str (String.v ~len:(-1)) (fun i -> 'a');
  app_invalid ~pp:pp_str (String.v ~len:(Sys.max_string_length + 1))
    (fun i -> 'a');
  eq_int (String.length "") 0;
  eq_int (String.length "1") 1;
  eq_int (String.length "12") 2;
  eq_char (String.get "12" 0) '1';
  eq_char (String.get "12" 1) '2';
  app_invalid ~pp:pp_char (String.get "12") 3;
  app_invalid ~pp:pp_char (String.get "12") (-1);
  eq_int (String.get_byte "12" 0) 0x31;
  eq_int (String.get_byte "12" 1) 0x32;
  app_invalid ~pp:pp_int (String.get_byte "12") 3;
  app_invalid ~pp:pp_int (String.get_byte "12") (-1);
  eq_str (String.v ~len:3 (fun i -> Char.of_byte (0x30 + i))) "012";
  eq_str (String.v ~len:0 (fun i -> Char.of_byte (0x30 + i))) "";
  ()

let head = test "String.[get_]head" @@ fun () ->
  let eq_ochar = eq_option ~eq:(=) ~pp:pp_char in
  eq_ochar (String.head "") None;
  eq_ochar (String.head ~rev:true "") None;
  eq_ochar (String.head "bc") (Some 'b');
  eq_ochar (String.head ~rev:true "bc") (Some 'c');
  eq_char (String.get_head "bc") 'b';
  eq_char (String.get_head ~rev:true "bc") 'c';
  app_invalid ~pp:pp_char String.get_head "";
  ()

(* Appending strings *)

let append = test "String.append" @@ fun () ->
  let no_allocl s s' = eq_bool (s ^ s' == s) true in
  let no_allocr s s' = eq_bool (s ^ s' == s') true in
  no_allocl String.empty String.empty;
  no_allocr String.empty String.empty;
  no_allocl "bla" "";
  no_allocr "" "bli";
  eq_str (String.append "a" "") "a";
  eq_str (String.append "" "a") "a";
  eq_str (String.append "ab" "") "ab";
  eq_str (String.append "" "ab") "ab";
  eq_str (String.append "ab" "cd") "abcd";
  eq_str (String.append "cd" "ab") "cdab";
  ()

let concat = test "String.concat" @@ fun () ->
  let no_alloc ~sep s = eq_bool (String.concat ~sep [s] == s) true in
  no_alloc ~sep:"" "";
  no_alloc ~sep:"-" "";
  no_alloc ~sep:"" "abc";
  no_alloc ~sep:"-" "abc";
  eq_str (String.concat ~sep:"" []) "";
  eq_str (String.concat ~sep:"" [""]) "";
  eq_str (String.concat ~sep:"" ["";""]) "";
  eq_str (String.concat ~sep:"" ["a";"b";]) "ab";
  eq_str (String.concat ~sep:"" ["a";"b";"";"c"]) "abc";
  eq_str (String.concat ~sep:"-" []) "";
  eq_str (String.concat ~sep:"-" [""]) "";
  eq_str (String.concat ~sep:"-" ["a"]) "a";
  eq_str (String.concat ~sep:"-" ["a";""]) "a-";
  eq_str (String.concat ~sep:"-" ["";"a"]) "-a";
  eq_str (String.concat ~sep:"-" ["";"a";""]) "-a-";
  eq_str (String.concat ~sep:"-" ["a";"b";"c"]) "a-b-c";
  eq_str (String.concat ~sep:"--" ["a";"b";"c"]) "a--b--c";
  eq_str (String.concat ~sep:"ab" ["a";"b";"c"]) "aabbabc";
  eq_str (String.concat ["a";"b";""; "c"]) "abc";
  ()

(* Predicates *)

let is_empty = test "String.is_empty" @@ fun () ->
  eq_bool (String.is_empty "") true;
  eq_bool (String.is_empty "heyho") false;
  ()

let is_prefix = test "String.is_prefix" @@ fun () ->
  eq_bool (String.is_prefix ~affix:"" "") true;
  eq_bool (String.is_prefix ~affix:"" "habla") true;
  eq_bool (String.is_prefix ~affix:"ha" "") false;
  eq_bool (String.is_prefix ~affix:"ha" "h") false;
  eq_bool (String.is_prefix ~affix:"ha" "ha") true;
  eq_bool (String.is_prefix ~affix:"ha" "hab") true;
  eq_bool (String.is_prefix ~affix:"ha" "habla") true;
  eq_bool (String.is_prefix ~affix:"ha" "abla") false;
  ()

let is_infix = test "String.is_infix" @@ fun () ->
  eq_bool (String.is_infix ~affix:"" "") true;
  eq_bool (String.is_infix ~affix:"" "habla") true;
  eq_bool (String.is_infix ~affix:"ha" "") false;
  eq_bool (String.is_infix ~affix:"ha" "h") false;
  eq_bool (String.is_infix ~affix:"ha" "ha") true;
  eq_bool (String.is_infix ~affix:"ha" "hab") true;
  eq_bool (String.is_infix ~affix:"ha" "hub") false;
  eq_bool (String.is_infix ~affix:"ha" "hubhab") true;
  eq_bool (String.is_infix ~affix:"ha" "hubh") false;
  eq_bool (String.is_infix ~affix:"ha" "hubha") true;
  eq_bool (String.is_infix ~affix:"ha" "hubhb") false;
  eq_bool (String.is_infix ~affix:"ha" "abla") false;
  eq_bool (String.is_infix ~affix:"ha" "ablah") false;
  ()

let is_suffix = test "String.is_suffix" @@ fun () ->
  eq_bool (String.is_suffix ~affix:"" "") true;
  eq_bool (String.is_suffix ~affix:"" "adsf") true;
  eq_bool (String.is_suffix ~affix:"ha" "") false;
  eq_bool (String.is_suffix ~affix:"ha" "a") false;
  eq_bool (String.is_suffix ~affix:"ha" "h") false;
  eq_bool (String.is_suffix ~affix:"ha" "ah") false;
  eq_bool (String.is_suffix ~affix:"ha" "ha") true;
  eq_bool (String.is_suffix ~affix:"ha" "aha") true;
  eq_bool (String.is_suffix ~affix:"ha" "haha") true;
  eq_bool (String.is_suffix ~affix:"ha" "hahb") false;
  ()

let for_all = test "String.for_all" @@ fun () ->
  eq_bool (String.for_all (fun _ -> false) "") true;
  eq_bool (String.for_all (fun _ -> true) "") true;
  eq_bool (String.for_all (fun c -> Char.to_int c < 0x34) "123") true;
  eq_bool (String.for_all (fun c -> Char.to_int c < 0x34) "412") false;
  eq_bool (String.for_all (fun c -> Char.to_int c < 0x34) "142") false;
  eq_bool (String.for_all (fun c -> Char.to_int c < 0x34) "124") false;
  ()

let exists = test "String.exists" @@ fun () ->
  eq_bool (String.exists (fun _ -> false) "") false;
  eq_bool (String.exists (fun _ -> true) "") false;
  eq_bool (String.exists (fun c -> Char.to_int c < 0x34) "541") true;
  eq_bool (String.exists (fun c -> Char.to_int c < 0x34) "541") true;
  eq_bool (String.exists (fun c -> Char.to_int c < 0x34) "154") true;
  eq_bool (String.exists (fun c -> Char.to_int c < 0x34) "654") false;
  ()

let equal = test "String.equal" @@ fun () ->
  eq_bool (String.equal "" "") true;
  eq_bool (String.equal "" "a") false;
  eq_bool (String.equal "a" "") false;
  eq_bool (String.equal "ab" "ab") true;
  eq_bool (String.equal "cd" "ab") false;
  ()

let compare = test "String.compare" @@ fun () ->
  eq_int (String.compare "" "ab") (-1);
  eq_int (String.compare "" "") (0);
  eq_int (String.compare "ab" "") (1);
  eq_int (String.compare "ab" "abc") (-1);
  ()

(* Extracting substrings *)

let with_range = test "String.with_range" @@ fun () ->
  let no_alloc ?first ?len s =
    eq_bool (String.with_range s ?first ?len == s ||
             String.(equal empty s)) true
  in
  let is_empty ?first ?len s =
    let s = String.with_range ?first ?len s in
    eq_str s String.empty;
    eq_bool (s == String.empty) true;
  in
  let invalid ?first ?len s =
    app_invalid ~pp:pp_str (String.with_range ?first ?len) s
  in
  let eq_range ?first ?len s s' = eq_str (String.with_range ?first ?len s) s' in
  no_alloc "";
  invalid  "" ~len:(-1);
  no_alloc "" ~len:0;
  no_alloc "" ~len:1;
  no_alloc "" ~len:2;
  no_alloc "" ~first:(-1);
  no_alloc "" ~first:0;
  no_alloc "" ~first:1;
  invalid  "" ~first:(-1) ~len:(-1);
  no_alloc "" ~first:(-1) ~len:0;
  no_alloc "" ~first:(-1) ~len:1;
  invalid  "" ~first:0 ~len:(-1);
  no_alloc "" ~first:0 ~len:0;
  no_alloc "" ~first:0 ~len:1;
  invalid  "" ~first:1 ~len:(-1);
  no_alloc "" ~first:1 ~len:0;
  no_alloc "" ~first:1 ~len:1;
  no_alloc "a";
  invalid  "a" ~len:(-1);
  is_empty "a" ~len:0;
  no_alloc "a" ~len:1;
  no_alloc "a" ~len:2;
  no_alloc "a" ~first:(-1);
  no_alloc "a" ~first:0;
  is_empty "a" ~first:1;
  invalid  "a" ~first:(-1) ~len:(-1);
  is_empty "a" ~first:(-1) ~len:0;
  is_empty "a" ~first:(-1) ~len:1;
  no_alloc "a" ~first:(-1) ~len:2;
  no_alloc "a" ~first:(-1) ~len:3;
  invalid  "a" ~first:0 ~len:(-1);
  is_empty "a" ~first:0 ~len:0;
  no_alloc "a" ~first:0 ~len:1;
  no_alloc "a" ~first:0 ~len:2;
  no_alloc "a" ~first:0 ~len:3;
  invalid  "a" ~first:1 ~len:(-1);
  is_empty "a" ~first:1 ~len:0;
  is_empty "a" ~first:1 ~len:1;
  is_empty "a" ~first:1 ~len:2;
  is_empty "a" ~first:1 ~len:3;
  no_alloc "ab";
  invalid  "ab" ~len:(-1);
  is_empty "ab" ~len:0;
  eq_range "ab" ~len:1 "a";
  no_alloc "ab" ~len:2;
  no_alloc "ab" ~len:3;
  no_alloc "ab" ~first:(-1);
  no_alloc "ab" ~first:0;
  eq_range "ab" ~first:1 "b";
  is_empty "ab" ~first:2;
  invalid  "ab" ~first:(-1) ~len:(-1);
  is_empty "ab" ~first:(-1) ~len:0;
  is_empty "ab" ~first:(-1) ~len:1;
  eq_range "ab" ~first:(-1) ~len:2 "a";
  no_alloc "ab" ~first:(-1) ~len:3;
  no_alloc "ab" ~first:(-1) ~len:4;
  invalid  "ab" ~first:0 ~len:(-1);
  is_empty "ab" ~first:0 ~len:0;
  eq_range "ab" ~first:0 ~len:1 "a";
  no_alloc "ab" ~first:0 ~len:2;
  no_alloc "ab" ~first:0 ~len:3;
  no_alloc "ab" ~first:0 ~len:4;
  invalid  "ab" ~first:1 ~len:(-1);
  is_empty "ab" ~first:1 ~len:0;
  eq_range "ab" ~first:1 ~len:1 "b";
  eq_range "ab" ~first:1 ~len:2 "b";
  eq_range "ab" ~first:1 ~len:3 "b";
  eq_range "ab" ~first:1 ~len:4 "b";
  invalid  "ab" ~first:2 ~len:(-1);
  is_empty "ab" ~first:2 ~len:0;
  is_empty "ab" ~first:2 ~len:1;
  is_empty "ab" ~first:2 ~len:2;
  is_empty "ab" ~first:2 ~len:3;
  is_empty "ab" ~first:2 ~len:4;
  no_alloc "abc";
  invalid  "abc" ~len:(-1);
  is_empty "abc" ~len:0;
  eq_range "abc" ~len:1 "a";
  eq_range "abc" ~len:2 "ab";
  no_alloc "abc" ~len:3;
  no_alloc "abc" ~len:4;
  no_alloc "abc" ~first:(-1);
  no_alloc "abc" ~first:0;
  eq_range "abc" ~first:1 "bc";
  eq_range "abc" ~first:2 "c";
  is_empty "abc" ~first:3;
  invalid  "abc" ~first:(-1) ~len:(-1);
  is_empty "abc" ~first:(-1) ~len:0;
  is_empty "abc" ~first:(-1) ~len:1;
  eq_range "abc" ~first:(-1) ~len:2 "a";
  eq_range "abc" ~first:(-1) ~len:3 "ab";
  eq_range "abc" ~first:(-1) ~len:4 "abc";
  no_alloc "abc" ~first:(-1) ~len:5;
  invalid  "abc" ~first:0 ~len:(-1);
  is_empty "abc" ~first:0 ~len:0;
  eq_range "abc" ~first:0 ~len:1 "a";
  eq_range "abc" ~first:0 ~len:2 "ab";
  no_alloc "abc" ~first:0 ~len:3;
  no_alloc "abc" ~first:0 ~len:4;
  no_alloc "abc" ~first:0 ~len:5;
  invalid  "abc" ~first:1 ~len:(-1);
  is_empty "abc" ~first:1 ~len:0;
  eq_range "abc" ~first:1 ~len:1 "b";
  eq_range "abc" ~first:1 ~len:2 "bc";
  eq_range "abc" ~first:1 ~len:3 "bc";
  eq_range "abc" ~first:1 ~len:4 "bc";
  eq_range "abc" ~first:1 ~len:5 "bc";
  invalid  "abc" ~first:2 ~len:(-1);
  is_empty "abc" ~first:2 ~len:0;
  eq_range "abc" ~first:2 ~len:1 "c";
  eq_range "abc" ~first:2 ~len:2 "c";
  eq_range "abc" ~first:2 ~len:3 "c";
  eq_range "abc" ~first:2 ~len:4 "c";
  eq_range "abc" ~first:2 ~len:5 "c";
  invalid  "abc" ~first:3 ~len:(-1);
  is_empty "abc" ~first:3 ~len:0;
  is_empty "abc" ~first:3 ~len:1;
  is_empty "abc" ~first:3 ~len:2;
  is_empty "abc" ~first:3 ~len:3;
  is_empty "abc" ~first:3 ~len:4;
  is_empty "abc" ~first:3 ~len:5;
  ()

let with_index_range = test "String.with_index_range" @@ fun () ->
  let no_alloc ?first ?last s =
    eq_bool (String.with_index_range s ?first ?last == s ||
             String.(equal empty s)) true
  in
  let is_empty ?first ?last s =
    let s = String.with_index_range ?first ?last s in
    eq_str s String.empty;
    eq_bool (s == String.empty) true;
  in
  let eq_range ?first ?last s s' =
    eq_str (String.with_index_range ?first ?last s) s'
  in
  no_alloc "";
  no_alloc "" ~first:(-1);
  no_alloc "" ~first:0;
  no_alloc "" ~first:1;
  no_alloc "" ~first:2;
  no_alloc "" ~last:(-1);
  no_alloc "" ~last:0;
  no_alloc "" ~last:1;
  no_alloc "" ~last:2;
  no_alloc "" ~first:(-1) ~last:(-1);
  no_alloc "" ~first:(-1) ~last:0;
  no_alloc "" ~first:(-1) ~last:1;
  no_alloc "" ~first:0 ~last:(-1);
  no_alloc "" ~first:0 ~last:0;
  no_alloc "" ~first:0 ~last:1;
  no_alloc "" ~first:1 ~last:(-1);
  no_alloc "" ~first:1 ~last:0;
  no_alloc "" ~first:1 ~last:1;
  no_alloc "a";
  no_alloc "a" ~first:(-1);
  no_alloc "a" ~first:0;
  is_empty "a" ~first:1;
  is_empty "a" ~first:2;
  is_empty "a" ~last:(-1);
  no_alloc "a" ~last:0;
  no_alloc "a" ~last:1;
  no_alloc "a" ~last:2;
  is_empty "a" ~first:(-1) ~last:(-1);
  no_alloc "a" ~first:(-1) ~last:0;
  no_alloc "a" ~first:(-1) ~last:1;
  no_alloc "a" ~first:(-1) ~last:2;
  no_alloc "a" ~first:(-1) ~last:3;
  is_empty "a" ~first:0 ~last:(-1);
  no_alloc "a" ~first:0 ~last:0;
  no_alloc "a" ~first:0 ~last:1;
  no_alloc "a" ~first:0 ~last:2;
  no_alloc "a" ~first:0 ~last:3;
  is_empty "a" ~first:1 ~last:(-1);
  is_empty "a" ~first:1 ~last:0;
  is_empty "a" ~first:1 ~last:1;
  is_empty "a" ~first:1 ~last:2;
  is_empty "a" ~first:1 ~last:3;
  no_alloc "ab";
  no_alloc "ab" ~first:(-1);
  no_alloc "ab" ~first:0;
  eq_range "ab" ~first:1 "b";
  is_empty "ab" ~first:2;
  is_empty "ab" ~last:(-1);
  eq_range "ab" ~last:0 "a";
  no_alloc "ab" ~last:1;
  no_alloc "ab" ~last:2;
  no_alloc "ab" ~last:3;
  is_empty "ab" ~first:(-1) ~last:(-1);
  eq_range "ab" ~first:(-1) ~last:0 "a";
  no_alloc "ab" ~first:(-1) ~last:1;
  no_alloc "ab" ~first:(-1) ~last:2;
  no_alloc "ab" ~first:(-1) ~last:3;
  no_alloc "ab" ~first:(-1) ~last:4;
  is_empty "ab" ~first:0 ~last:(-1);
  eq_range "ab" ~first:0 ~last:0 "a";
  no_alloc "ab" ~first:0 ~last:1;
  no_alloc "ab" ~first:0 ~last:2;
  no_alloc "ab" ~first:0 ~last:3;
  no_alloc "ab" ~first:0 ~last:4;
  is_empty "ab" ~first:1 ~last:(-1);
  is_empty "ab" ~first:1 ~last:0;
  eq_range "ab" ~first:1 ~last:1 "b";
  eq_range "ab" ~first:1 ~last:2 "b";
  eq_range "ab" ~first:1 ~last:3 "b";
  eq_range "ab" ~first:1 ~last:4 "b";
  is_empty "ab" ~first:2 ~last:(-1);
  is_empty "ab" ~first:2 ~last:0;
  is_empty "ab" ~first:2 ~last:1;
  is_empty "ab" ~first:2 ~last:2;
  is_empty "ab" ~first:2 ~last:3;
  is_empty "ab" ~first:2 ~last:4;
  no_alloc "abc";
  no_alloc "abc" ~first:(-1);
  no_alloc "abc" ~first:0;
  eq_range "abc" ~first:1 "bc";
  eq_range "abc" ~first:2 "c";
  is_empty "abc" ~first:3;
  is_empty "abc" ~last:(-1);
  eq_range "abc" ~last:0 "a";
  eq_range "abc" ~last:1 "ab";
  no_alloc "abc" ~last:2;
  no_alloc "abc" ~last:3;
  no_alloc "abc" ~last:4;
  is_empty "abc" ~first:(-1) ~last:(-1);
  eq_range "abc" ~first:(-1) ~last:0 "a";
  eq_range "abc" ~first:(-1) ~last:1 "ab";
  no_alloc "abc" ~first:(-1) ~last:2;
  no_alloc "abc" ~first:(-1) ~last:3;
  no_alloc "abc" ~first:(-1) ~last:4;
  no_alloc "abc" ~first:(-1) ~last:5;
  is_empty "abc" ~first:0 ~last:(-1);
  eq_range "abc" ~first:0 ~last:0 "a";
  eq_range "abc" ~first:0 ~last:1 "ab";
  no_alloc "abc" ~first:0 ~last:2;
  no_alloc "abc" ~first:0 ~last:3;
  no_alloc "abc" ~first:0 ~last:4;
  no_alloc "abc" ~first:0 ~last:5;
  is_empty "abc" ~first:1 ~last:(-1);
  is_empty "abc" ~first:1 ~last:0;
  eq_range "abc" ~first:1 ~last:1 "b";
  eq_range "abc" ~first:1 ~last:2 "bc";
  eq_range "abc" ~first:1 ~last:3 "bc";
  eq_range "abc" ~first:1 ~last:4 "bc";
  eq_range "abc" ~first:1 ~last:5 "bc";
  is_empty "abc" ~first:2 ~last:(-1);
  is_empty "abc" ~first:2 ~last:0;
  is_empty "abc" ~first:2 ~last:1;
  eq_range "abc" ~first:2 ~last:2 "c";
  eq_range "abc" ~first:2 ~last:3 "c";
  eq_range "abc" ~first:2 ~last:4 "c";
  eq_range "abc" ~first:2 ~last:5 "c";
  is_empty "abc" ~first:3 ~last:(-1);
  is_empty "abc" ~first:3 ~last:0;
  is_empty "abc" ~first:3 ~last:1;
  is_empty "abc" ~first:3 ~last:2;
  is_empty "abc" ~first:3 ~last:3;
  is_empty "abc" ~first:3 ~last:4;
  is_empty "abc" ~first:3 ~last:5;
  ()

let trim = test "String.trim" @@ fun () ->
  let drop_a c = c = 'a' in
  let no_alloc ?drop s = eq_bool (String.trim ?drop s == s) true in
  no_alloc "";
  no_alloc ~drop:drop_a "";
  no_alloc "bc";
  no_alloc ~drop:drop_a "bc";
  eq_str (String.trim "\t abcd \r ") "abcd";
  no_alloc ~drop:drop_a "\x00 abcd \x1F ";
  no_alloc "aaaabcdaaaa";
  eq_str (String.trim ~drop:drop_a "aaaabcdaaaa") "bcd";
  eq_str (String.trim ~drop:drop_a "aaaabcd") "bcd";
  eq_str (String.trim ~drop:drop_a "bcdaaaa") "bcd";
  eq_str (String.trim ~drop:drop_a "aaaa") "";
  eq_str (String.trim "     ") "";
  ()

let span = test "String.{span,take,drop}" @@ fun () ->
  let eq_pair (l0, r0) (l1, r1) = String.equal l0 l1 && String.equal r0 r1 in
  let eq_pair = eq ~eq:eq_pair ~pp:pp_pair in
  let eq ?(rev = false) ?min ?max ?sat s (sl, sr as spec) =
    let (l, r as pair) = String.span ~rev ?min ?max ?sat s in
    let t = String.take ~rev ?min ?max ?sat s in
    let d = String.drop ~rev ?min ?max ?sat s in
    eq_pair pair spec;
    eq_str t (if rev then sr else sl);
    eq_str d (if rev then sl else sr);
    if sl = "" then begin
      eq_bool (l == String.empty) true;
      eq_bool (r == s) true;
      eq_bool ((if rev then d else t) == String.empty) true;
    end;
    if sr = "" then begin
      eq_bool (r == String.empty) true;
      eq_bool (l == s) true;
      eq_bool ((if rev then t else d) == String.empty) true;
    end
  in
  let invalid ?rev ?min ?max ?sat s =
    app_invalid ~pp:pp_pair (String.span ?rev ?min ?max ?sat) s
  in
  invalid ~rev:false ~min:(-1) "";
  invalid ~rev:true  ~min:(-1) "";
  invalid ~rev:false ~max:(-1) "";
  invalid ~rev:true ~max:(-1) "";
  eq ~rev:false String.empty ("","");
  eq ~rev:true  String.empty ("","");
  eq ~rev:false ~min:0 ~max:0 String.empty ("","");
  eq ~rev:true  ~min:0 ~max:0 String.empty ("","");
  eq ~rev:false ~min:1 ~max:0 String.empty ("","");
  eq ~rev:true  ~min:1 ~max:0 String.empty ("","");
  eq ~rev:false ~max:0 "ab_cd" ("","ab_cd");
  eq ~rev:true  ~max:0 "ab_cd" ("ab_cd","");
  eq ~rev:false ~max:2 "ab_cd" ("ab", "_cd");
  eq ~rev:true  ~max:2 "ab_cd" ("ab_", "cd");
  eq ~rev:false ~min:6 "ab_cd" ("", "ab_cd");
  eq ~rev:true  ~min:6 "ab_cd" ("ab_cd", "");
  eq ~rev:false "ab_cd" ("ab_cd", "");
  eq ~rev:true  "ab_cd" ("", "ab_cd");
  eq ~rev:false ~max:30 "ab_cd" ("ab_cd", "");
  eq ~rev:true  ~max:30 "ab_cd" ("", "ab_cd");
  eq ~rev:false ~sat:Char.Ascii.is_white "ab_cd" ("","ab_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_white "ab_cd" ("ab_cd","");
  eq ~rev:false ~sat:Char.Ascii.is_letter "ab_cd" ("ab", "_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_letter "ab_cd" ("ab_", "cd");
  eq ~rev:false ~sat:Char.Ascii.is_letter ~max:0 "ab_cd" ("", "ab_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_letter ~max:0 "ab_cd" ("ab_cd", "");
  eq ~rev:false ~sat:Char.Ascii.is_letter ~max:1 "ab_cd" ("a", "b_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_letter ~max:1 "ab_cd" ("ab_c", "d");
  eq ~rev:false ~sat:Char.Ascii.is_letter ~min:2 ~max:1 "ab_cd" ("", "ab_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_letter ~min:2 ~max:1 "ab_cd" ("ab_cd", "");
  eq ~rev:false ~sat:Char.Ascii.is_letter ~min:3 "ab_cd" ("", "ab_cd");
  eq ~rev:true  ~sat:Char.Ascii.is_letter ~min:3 "ab_cd" ("ab_cd", "");
  ()

let cut = test "String.cut" @@ fun () ->
  let ppp = pp_option pp_pair in
  let eqo = eq_option ~eq:(=) ~pp:pp_pair in
  app_invalid ~pp:ppp (String.cut ~sep:"") "";
  app_invalid ~pp:ppp (String.cut ~sep:"") "123";
  eqo (String.cut "," "")  None;
  eqo (String.cut "," ",") (Some ("", ""));
  eqo (String.cut "," ",,") (Some ("", ","));
  eqo (String.cut "," ",,,") (Some ("", ",,"));
  eqo (String.cut "," "123") None;
  eqo (String.cut "," ",123") (Some ("", "123"));
  eqo (String.cut "," "123,") (Some ("123", ""));
  eqo (String.cut "," "1,2,3") (Some ("1", "2,3"));
  eqo (String.cut "," " 1,2,3") (Some (" 1", "2,3"));
  eqo (String.cut "<>" "") None;
  eqo (String.cut "<>" "<>") (Some ("", ""));
  eqo (String.cut "<>" "<><>") (Some ("", "<>"));
  eqo (String.cut "<>" "<><><>") (Some ("", "<><>"));
  eqo (String.cut ~rev:true ~sep:"<>" "1") None;
  eqo (String.cut "<>" "123") None;
  eqo (String.cut "<>" "<>123") (Some ("", "123"));
  eqo (String.cut "<>" "123<>") (Some ("123", ""));
  eqo (String.cut "<>" "1<>2<>3") (Some ("1", "2<>3"));
  eqo (String.cut "<>" " 1<>2<>3") (Some (" 1", "2<>3"));
  eqo (String.cut "<>" ">>><>>>><>>>><>>>>") (Some (">>>", ">>><>>>><>>>>"));
  eqo (String.cut "<->" "<->>->") (Some ("", ">->"));
  eqo (String.cut ~rev:true ~sep:"<->" "<-") None;
  eqo (String.cut "aa" "aa") (Some ("", ""));
  eqo (String.cut "aa" "aaa") (Some ("", "a"));
  eqo (String.cut "aa" "aaaa") (Some ("", "aa"));
  eqo (String.cut "aa" "aaaaa") (Some ("", "aaa";));
  eqo (String.cut "aa" "aaaaaa") (Some ("", "aaaa"));
  eqo (String.cut ~sep:"ab" "faaaa") None;
  let rev = true in
  app_invalid ~pp:ppp (String.cut ~rev ~sep:"") "";
  app_invalid ~pp:ppp (String.cut ~rev ~sep:"") "123";
  eqo (String.cut ~rev ~sep:"," "") None;
  eqo (String.cut ~rev ~sep:"," ",") (Some ("", ""));
  eqo (String.cut ~rev ~sep:"," ",,") (Some (",", ""));
  eqo (String.cut ~rev ~sep:"," ",,,") (Some (",,", ""));
  eqo (String.cut ~rev ~sep:"," "123") None;
  eqo (String.cut ~rev ~sep:"," ",123") (Some ("", "123"));
  eqo (String.cut ~rev ~sep:"," "123,") (Some ("123", ""));
  eqo (String.cut ~rev ~sep:"," "1,2,3") (Some ("1,2", "3"));
  eqo (String.cut ~rev ~sep:"," "1,2,3 ") (Some ("1,2", "3 "));
  eqo (String.cut ~rev ~sep:"<>" "") None;
  eqo (String.cut ~rev ~sep:"<>" "<>") (Some ("", ""));
  eqo (String.cut ~rev ~sep:"<>" "<><>") (Some ("<>", ""));
  eqo (String.cut ~rev ~sep:"<>" "<><><>") (Some ("<><>", ""));
  eqo (String.cut ~rev ~sep:"<>" "1") None;
  eqo (String.cut ~rev ~sep:"<>" "123") None;
  eqo (String.cut ~rev ~sep:"<>" "<>123") (Some ("", "123"));
  eqo (String.cut ~rev ~sep:"<>" "123<>") (Some ("123", ""));
  eqo (String.cut ~rev ~sep:"<>" "1<>2<>3") (Some ("1<>2", "3"));
  eqo (String.cut ~rev ~sep:"<>" "1<>2<>3 ") (Some ("1<>2", "3 "));
  eqo (String.cut ~rev ~sep:"<>" ">>><>>>><>>>><>>>>")
    (Some (">>><>>>><>>>>", ">>>"));
  eqo (String.cut ~rev ~sep:"<->" "<->>->") (Some ("", ">->"));
  eqo (String.cut ~rev ~sep:"<->" "<-") None;
  eqo (String.cut ~rev ~sep:"aa" "aa") (Some ("", ""));
  eqo (String.cut ~rev ~sep:"aa" "aaa") (Some ("a", ""));
  eqo (String.cut ~rev ~sep:"aa" "aaaa") (Some ("aa", ""));
  eqo (String.cut ~rev ~sep:"aa" "aaaaa") (Some ("aaa", "";));
  eqo (String.cut ~rev ~sep:"aa" "aaaaaa") (Some ("aaaa", ""));
  eqo (String.cut ~rev ~sep:"ab" "afaaaa") None;
  ()

let cuts = test "String.cuts" @@ fun () ->
  let ppl = pp_list String.dump in
  let eql = eq_list ~eq:String.equal ~pp:String.dump in
  let no_alloc ?rev ~sep s =
    eq_bool (List.hd (String.cuts ?rev ~sep s) == s) true
  in
  app_invalid ~pp:ppl (String.cuts ~sep:"") "";
  app_invalid ~pp:ppl (String.cuts ~sep:"") "123";
  no_alloc ~sep:"," "";
  no_alloc ~sep:"," "abcd";
  eql (String.cuts ~empty:true  ~sep:"," "") [""];
  eql (String.cuts ~empty:false ~sep:"," "") [];
  eql (String.cuts ~empty:true  ~sep:"," ",") [""; ""];
  eql (String.cuts ~empty:false ~sep:"," ",") [];
  eql (String.cuts ~empty:true  ~sep:"," ",,") [""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"," ",,") [];
  eql (String.cuts ~empty:true  ~sep:"," ",,,") [""; ""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"," ",,,") [];
  eql (String.cuts ~empty:true  ~sep:"," "123") ["123"];
  eql (String.cuts ~empty:false ~sep:"," "123") ["123"];
  eql (String.cuts ~empty:true  ~sep:"," ",123") [""; "123"];
  eql (String.cuts ~empty:false ~sep:"," ",123") ["123"];
  eql (String.cuts ~empty:true  ~sep:"," "123,") ["123"; ""];
  eql (String.cuts ~empty:false ~sep:"," "123,") ["123";];
  eql (String.cuts ~empty:true  ~sep:"," "1,2,3") ["1"; "2"; "3"];
  eql (String.cuts ~empty:false ~sep:"," "1,2,3") ["1"; "2"; "3"];
  eql (String.cuts ~empty:true  ~sep:"," "1, 2, 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~empty:false  ~sep:"," "1, 2, 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~empty:true ~sep:"," ",1,2,,3,") [""; "1"; "2"; ""; "3"; ""];
  eql (String.cuts ~empty:false ~sep:"," ",1,2,,3,") ["1"; "2"; "3";];
  eql (String.cuts ~empty:true ~sep:"," ", 1, 2,, 3,")
    [""; " 1"; " 2"; ""; " 3"; ""];
  eql (String.cuts ~empty:false ~sep:"," ", 1, 2,, 3,") [" 1"; " 2";" 3";];
  eql (String.cuts ~empty:true ~sep:"<>" "") [""];
  eql (String.cuts ~empty:false ~sep:"<>" "") [];
  eql (String.cuts ~empty:true ~sep:"<>" "<>") [""; ""];
  eql (String.cuts ~empty:false ~sep:"<>" "<>") [];
  eql (String.cuts ~empty:true ~sep:"<>" "<><>") [""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"<>" "<><>") [];
  eql (String.cuts ~empty:true ~sep:"<>" "<><><>") [""; ""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"<>" "<><><>") [];
  eql (String.cuts ~empty:true ~sep:"<>" "123") [ "123" ];
  eql (String.cuts ~empty:false ~sep:"<>" "123") [ "123" ];
  eql (String.cuts ~empty:true ~sep:"<>" "<>123") [""; "123"];
  eql (String.cuts ~empty:false ~sep:"<>" "<>123") ["123"];
  eql (String.cuts ~empty:true ~sep:"<>" "123<>") ["123"; ""];
  eql (String.cuts ~empty:false ~sep:"<>" "123<>") ["123"];
  eql (String.cuts ~empty:true ~sep:"<>" "1<>2<>3") ["1"; "2"; "3"];
  eql (String.cuts ~empty:false ~sep:"<>" "1<>2<>3") ["1"; "2"; "3"];
  eql (String.cuts ~empty:true ~sep:"<>" "1<> 2<> 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~empty:false ~sep:"<>" "1<> 2<> 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~empty:true ~sep:"<>" "<>1<>2<><>3<>")
    [""; "1"; "2"; ""; "3"; ""];
  eql (String.cuts ~empty:false ~sep:"<>" "<>1<>2<><>3<>") ["1"; "2";"3";];
  eql (String.cuts ~empty:true ~sep:"<>" "<> 1<> 2<><> 3<>")
    [""; " 1"; " 2"; ""; " 3";""];
  eql (String.cuts ~empty:false ~sep:"<>" "<> 1<> 2<><> 3<>")[" 1"; " 2"; " 3"];
  eql (String.cuts ~empty:true ~sep:"<>" ">>><>>>><>>>><>>>>")
    [">>>"; ">>>"; ">>>"; ">>>" ];
  eql (String.cuts ~empty:false ~sep:"<>" ">>><>>>><>>>><>>>>")
    [">>>"; ">>>"; ">>>"; ">>>" ];
  eql (String.cuts ~empty:true ~sep:"<->" "<->>->") [""; ">->"];
  eql (String.cuts ~empty:false ~sep:"<->" "<->>->") [">->"];
  eql (String.cuts ~empty:true ~sep:"aa" "aa") [""; ""];
  eql (String.cuts ~empty:false ~sep:"aa" "aa") [];
  eql (String.cuts ~empty:true ~sep:"aa" "aaa") [""; "a"];
  eql (String.cuts ~empty:false ~sep:"aa" "aaa") ["a"];
  eql (String.cuts ~empty:true ~sep:"aa" "aaaa") [""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"aa" "aaaa") [];
  eql (String.cuts ~empty:true ~sep:"aa" "aaaaa") [""; ""; "a"];
  eql (String.cuts ~empty:false ~sep:"aa" "aaaaa") ["a"];
  eql (String.cuts ~empty:true ~sep:"aa" "aaaaaa") [""; ""; ""; ""];
  eql (String.cuts ~empty:false ~sep:"aa" "aaaaaa") [];
  let rev = true in
  app_invalid ~pp:ppl (String.cuts ~rev ~sep:"") "";
  app_invalid ~pp:ppl (String.cuts ~rev ~sep:"") "123";
  no_alloc ~rev ~sep:"," "";
  no_alloc ~rev ~sep:"," "abcd";
  eql (String.cuts ~rev ~empty:true ~sep:"," "") [""];
  eql (String.cuts ~rev ~empty:false ~sep:"," "") [];
  eql (String.cuts ~rev ~empty:true ~sep:"," ",") [""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," ",") [];
  eql (String.cuts ~rev ~empty:true ~sep:"," ",,") [""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," ",,") [];
  eql (String.cuts ~rev ~empty:true ~sep:"," ",,,") [""; ""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," ",,,") [];
  eql (String.cuts ~rev ~empty:true ~sep:"," "123") ["123"];
  eql (String.cuts ~rev ~empty:false ~sep:"," "123") ["123"];
  eql (String.cuts ~rev ~empty:true ~sep:"," ",123") [""; "123"];
  eql (String.cuts ~rev ~empty:false ~sep:"," ",123") ["123"];
  eql (String.cuts ~rev ~empty:true ~sep:"," "123,") ["123"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," "123,") ["123";];
  eql (String.cuts ~rev ~empty:true ~sep:"," "1,2,3") ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:false ~sep:"," "1,2,3") ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:true ~sep:"," "1, 2, 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~rev ~empty:false ~sep:"," "1, 2, 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~rev ~empty:true ~sep:"," ",1,2,,3,")
    [""; "1"; "2"; ""; "3"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," ",1,2,,3,") ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:true ~sep:"," ", 1, 2,, 3,")
    [""; " 1"; " 2"; ""; " 3"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"," ", 1, 2,, 3,") [" 1"; " 2"; " 3"];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "") [""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "") [];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<>") [""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<>") [];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<><>") [""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<><>") [];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<><><>") [""; ""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<><><>") [];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "123") [ "123" ];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "123") [ "123" ];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<>123") [""; "123"];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<>123") ["123"];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "123<>") ["123"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "123<>") ["123";];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "1<>2<>3") ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "1<>2<>3") ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "1<> 2<> 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "1<> 2<> 3") ["1"; " 2"; " 3"];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<>1<>2<><>3<>")
    [""; "1"; "2"; ""; "3"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<>1<>2<><>3<>")
    ["1"; "2"; "3"];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" "<> 1<> 2<><> 3<>")
                                  [""; " 1"; " 2"; ""; " 3";""];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" "<> 1<> 2<><> 3<>")
                                  [" 1"; " 2"; " 3";];
  eql (String.cuts ~rev ~empty:true ~sep:"<>" ">>><>>>><>>>><>>>>")
                                  [">>>"; ">>>"; ">>>"; ">>>" ];
  eql (String.cuts ~rev ~empty:false ~sep:"<>" ">>><>>>><>>>><>>>>")
                                  [">>>"; ">>>"; ">>>"; ">>>" ];
  eql (String.cuts ~rev ~empty:true ~sep:"<->" "<->>->") [""; ">->"];
  eql (String.cuts ~rev ~empty:false ~sep:"<->" "<->>->") [">->"];
  eql (String.cuts ~rev ~empty:true ~sep:"aa" "aa") [""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"aa" "aa") [];
  eql (String.cuts ~rev ~empty:true ~sep:"aa" "aaa") ["a"; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"aa" "aaa") ["a"];
  eql (String.cuts ~rev ~empty:true ~sep:"aa" "aaaa") [""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"aa" "aaaa") [];
  eql (String.cuts ~rev ~empty:true ~sep:"aa" "aaaaa") ["a"; ""; "";];
  eql (String.cuts ~rev ~empty:false ~sep:"aa" "aaaaa") ["a";];
  eql (String.cuts ~rev ~empty:true ~sep:"aa" "aaaaaa") [""; ""; ""; ""];
  eql (String.cuts ~rev ~empty:false ~sep:"aa" "aaaaaa") [];
  ()

let fields = test "String.fields" @@ fun () ->
  let eql = eq_list ~eq:String.equal ~pp:String.dump in
  let no_alloc ?empty ?is_sep s =
    eq_bool (List.hd (String.fields ?empty ?is_sep s) == s) true
  in
  let is_a c = c = 'a' in
  no_alloc ~empty:true "a";
  no_alloc ~empty:false "a";
  no_alloc ~empty:true "abc";
  no_alloc ~empty:false "abc";
  no_alloc ~empty:true ~is_sep:is_a "bcdf";
  no_alloc ~empty:false ~is_sep:is_a "bcdf";
  eql (String.fields ~empty:true "") [""];
  eql (String.fields ~empty:false "") [];
  eql (String.fields ~empty:true "\n\r") ["";"";""];
  eql (String.fields ~empty:false "\n\r") [];
  eql (String.fields ~empty:true " \n\rabc") ["";"";"";"abc"];
  eql (String.fields ~empty:false " \n\rabc") ["abc"];
  eql (String.fields ~empty:true " \n\racd de") ["";"";"";"acd";"de"];
  eql (String.fields ~empty:false " \n\racd de") ["acd";"de"];
  eql (String.fields ~empty:true " \n\racd de ") ["";"";"";"acd";"de";""];
  eql (String.fields ~empty:false " \n\racd de ") ["acd";"de"];
  eql (String.fields ~empty:true "\n\racd\nde \r") ["";"";"acd";"de";"";""];
  eql (String.fields ~empty:false "\n\racd\nde \r") ["acd";"de"];
  eql (String.fields ~empty:true ~is_sep:is_a "") [""];
  eql (String.fields ~empty:false ~is_sep:is_a "") [];
  eql (String.fields ~empty:true ~is_sep:is_a "abaac aaa")
    ["";"b";"";"c ";"";"";""];
  eql (String.fields ~empty:false ~is_sep:is_a "abaac aaa") ["b"; "c "];
  eql (String.fields ~empty:true ~is_sep:is_a "aaaa") ["";"";"";"";""];
  eql (String.fields ~empty:false ~is_sep:is_a "aaaa") [];
  eql (String.fields ~empty:true ~is_sep:is_a "aaaa ") ["";"";"";"";" "];
  eql (String.fields ~empty:false ~is_sep:is_a "aaaa ") [" "];
  eql (String.fields ~empty:true ~is_sep:is_a "aaaab") ["";"";"";"";"b"];
  eql (String.fields ~empty:false ~is_sep:is_a "aaaab") ["b"];
  eql (String.fields ~empty:true ~is_sep:is_a "baaaa") ["b";"";"";"";""];
  eql (String.fields ~empty:false ~is_sep:is_a "baaaa") ["b"];
  eql (String.fields ~empty:true ~is_sep:is_a "abaaaa") ["";"b";"";"";"";""];
  eql (String.fields ~empty:false ~is_sep:is_a "abaaaa") ["b"];
  eql (String.fields ~empty:true ~is_sep:is_a "aba") ["";"b";""];
  eql (String.fields ~empty:false ~is_sep:is_a "aba") ["b"];
  eql (String.fields ~empty:false "tokenize me please")
    ["tokenize"; "me"; "please"];
  ()

(* Traversing strings *)

let find = test "String.find" @@ fun () ->
  let eq = eq_option ~eq:(=) ~pp:pp_int in
  let a c = c = 'a' in
  eq (String.find ~rev:false a "") None;
  eq (String.find ~rev:false ~start:(-1) a "") None;
  eq (String.find ~rev:false ~start:0 a "") None;
  eq (String.find ~rev:false ~start:1 a "") None;
  eq (String.find ~rev:true a "") None;
  eq (String.find ~rev:true ~start:(-1) a "") None;
  eq (String.find ~rev:true ~start:0 a "") None;
  eq (String.find ~rev:true ~start:1 a "") None;
  eq (String.find ~rev:false ~start:(-1) a "a") (Some 0);
  eq (String.find ~rev:false ~start:0 a "a") (Some 0);
  eq (String.find ~rev:false ~start:1 a "a") None;
  eq (String.find ~rev:true ~start:(-1) a "a") None;
  eq (String.find ~rev:true ~start:0 a "a") (Some 0);
  eq (String.find ~rev:true ~start:1 a "a") (Some 0);
  eq (String.find ~rev:false ~start:(-1) a "ba") (Some 1);
  eq (String.find ~rev:false ~start:0 a "ba") (Some 1);
  eq (String.find ~rev:false ~start:1 a "ba") (Some 1);
  eq (String.find ~rev:false ~start:2 a "ba") None;
  eq (String.find ~rev:true ~start:(-1) a "ba") None;
  eq (String.find ~rev:true ~start:0 a "ba") None;
  eq (String.find ~rev:true ~start:1 a "ba") (Some 1);
  eq (String.find ~rev:true ~start:2 a "ba") (Some 1);
  eq (String.find ~rev:true ~start:3 a "ba") (Some 1);
  eq (String.find ~rev:false a "aba") (Some 0);
  eq (String.find ~rev:false ~start:(-1) a "aba") (Some 0);
  eq (String.find ~rev:false ~start:0 a "aba") (Some 0);
  eq (String.find ~rev:false ~start:1 a "aba") (Some 2);
  eq (String.find ~rev:false ~start:2 a "aba") (Some 2);
  eq (String.find ~rev:false ~start:3 a "aba") None;
  eq (String.find ~rev:false ~start:4 a "aba") None;
  eq (String.find ~rev:true a "aba") (Some 2);
  eq (String.find ~rev:true ~start:(-1) a "aba") None;
  eq (String.find ~rev:true ~start:0 a "aba") (Some 0);
  eq (String.find ~rev:true ~start:1 a "aba") (Some 0);
  eq (String.find ~rev:true ~start:2 a "aba") (Some 2);
  eq (String.find ~rev:true ~start:3 a "aba") (Some 2);
  eq (String.find ~rev:true ~start:4 a "aba") (Some 2);
  eq (String.find ~rev:false a "bab") (Some 1);
  eq (String.find ~rev:false ~start:(-1) a "bab") (Some 1);
  eq (String.find ~rev:false ~start:0 a "bab") (Some 1);
  eq (String.find ~rev:false ~start:1 a "bab") (Some 1);
  eq (String.find ~rev:false ~start:2 a "bab") None;
  eq (String.find ~rev:false ~start:3 a "bab") None;
  eq (String.find ~rev:false ~start:4 a "bab") None;
  eq (String.find ~rev:true a "bab") (Some 1);
  eq (String.find ~rev:true ~start:(-1) a "bab") None;
  eq (String.find ~rev:true ~start:0 a "bab") None;
  eq (String.find ~rev:true ~start:1 a "bab") (Some 1);
  eq (String.find ~rev:true ~start:2 a "bab") (Some 1);
  eq (String.find ~rev:true ~start:3 a "bab") (Some 1);
  eq (String.find ~rev:true ~start:4 a "bab") (Some 1);
  eq (String.find ~rev:false a "baab") (Some 1);
  eq (String.find ~rev:false ~start:(-1) a "baab") (Some 1);
  eq (String.find ~rev:false ~start:0 a "baab") (Some 1);
  eq (String.find ~rev:false ~start:1 a "baab") (Some 1);
  eq (String.find ~rev:false ~start:2 a "baab") (Some 2);
  eq (String.find ~rev:false ~start:3 a "baab") None;
  eq (String.find ~rev:false ~start:4 a "baab") None;
  eq (String.find ~rev:false ~start:5 a "baab") None;
  eq (String.find ~rev:true ~start:(-1) a "baab") None;
  eq (String.find ~rev:true ~start:0 a "baab") None;
  eq (String.find ~rev:true ~start:1 a "baab") (Some 1);
  eq (String.find ~rev:true ~start:2 a "baab") (Some 2);
  eq (String.find ~rev:true ~start:3 a "baab") (Some 2);
  eq (String.find ~rev:true ~start:4 a "baab") (Some 2);
  eq (String.find ~rev:true ~start:5 a "baab") (Some 2);
  ()

let find_sub = test "String.find_sub" @@ fun () ->
  let eq = eq_option ~eq:(=) ~pp:pp_int in
  eq (String.find_sub ~rev:false ~sub:"" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~start:0 ~sub:"" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~start:1 ~sub:"" "ab") (Some 1);
  eq (String.find_sub ~rev:false ~start:2 ~sub:"" "ab") None;
  eq (String.find_sub ~rev:true ~sub:"" "ab") (Some 1);
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"" "ab") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"" "ab") (Some 0);
  eq (String.find_sub ~rev:true ~start:1 ~sub:"" "ab") (Some 1);
  eq (String.find_sub ~rev:true ~start:2 ~sub:"" "ab") (Some 1);
  eq (String.find_sub ~rev:false ~sub:"" "") None;
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"" "") None;
  eq (String.find_sub ~rev:false ~start:0 ~sub:"" "") None;
  eq (String.find_sub ~rev:false ~start:1 ~sub:"" "") None;
  eq (String.find_sub ~rev:true ~sub:"" "") None;
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"" "") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"" "") None;
  eq (String.find_sub ~rev:true ~start:1 ~sub:"" "") None;
  eq (String.find_sub ~rev:false ~sub:"ab" "") None;
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"ab" "") None;
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "") None;
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "") None;
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"ab" "") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "") None;
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "") None;
  eq (String.find_sub ~rev:false ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:false ~start:2 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:true ~start:2 ~sub:"ab" "a") None;
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "ab") None;
  eq (String.find_sub ~rev:false ~start:2 ~sub:"ab" "ab") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"ab" "ab") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:true ~start:2 ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:true ~start:3 ~sub:"ab" "ab") (Some 0);
  eq (String.find_sub ~rev:false ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "aba") None;
  eq (String.find_sub ~rev:false ~start:2 ~sub:"ab" "aba") None;
  eq (String.find_sub ~rev:false ~start:3 ~sub:"ab" "aba") None;
  eq (String.find_sub ~rev:false ~start:4 ~sub:"ab" "aba") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"ab" "aba") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:true ~start:2 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:true ~start:3 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:true ~start:4 ~sub:"ab" "aba") (Some 0);
  eq (String.find_sub ~rev:false ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:false ~start:2 ~sub:"ab" "bab") None;
  eq (String.find_sub ~rev:false ~start:3 ~sub:"ab" "bab") None;
  eq (String.find_sub ~rev:false ~start:4 ~sub:"ab" "bab") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"ab" "bab") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "bab") None;
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:true ~start:2 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:true ~start:3 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:true ~start:4 ~sub:"ab" "bab") (Some 1);
  eq (String.find_sub ~rev:false ~sub:"ab" "abab") (Some 0);
  eq (String.find_sub ~rev:false ~start:(-1) ~sub:"ab" "abab") (Some 0);
  eq (String.find_sub ~rev:false ~start:0 ~sub:"ab" "abab") (Some 0);
  eq (String.find_sub ~rev:false ~start:1 ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:false ~start:2 ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:false ~start:3 ~sub:"ab" "abab") None;
  eq (String.find_sub ~rev:false ~start:4 ~sub:"ab" "abab") None;
  eq (String.find_sub ~rev:false ~start:5 ~sub:"ab" "abab") None;
  eq (String.find_sub ~rev:true ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:true ~start:(-1) ~sub:"ab" "abab") None;
  eq (String.find_sub ~rev:true ~start:0 ~sub:"ab" "abab") (Some 0);
  eq (String.find_sub ~rev:true ~start:1 ~sub:"ab" "abab") (Some 0);
  eq (String.find_sub ~rev:true ~start:2 ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:true ~start:3 ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:true ~start:4 ~sub:"ab" "abab") (Some 2);
  eq (String.find_sub ~rev:true ~start:5 ~sub:"ab" "abab") (Some 2);
  ()

let filter = test "String.filter[_map]" @@ fun () ->
  let no_alloc k f s = eq_bool (k f s == s) true in
  no_alloc String.filter (fun _ -> true) "";
  no_alloc String.filter (fun _ -> true) "abcd";
  no_alloc String.filter_map (fun c -> Some c) "";
  no_alloc String.filter_map (fun c -> Some c) "abcd";
  let gen_filter :
    'a. ('a -> string -> string) -> 'a -> unit =
  fun filter a ->
    no_alloc filter a "";
    no_alloc filter a "a";
    no_alloc filter a "aa";
    no_alloc filter a "aaa";
    eq_str (filter a "ab") "a";
    eq_str (filter a "ba") "a";
    eq_str (filter a "abc") "a";
    eq_str (filter a "bac") "a";
    eq_str (filter a "bca") "a";
    eq_str (filter a "aba") "aa";
    eq_str (filter a "aab") "aa";
    eq_str (filter a "baa") "aa";
    eq_str (filter a "aabc") "aa";
    eq_str (filter a "abac") "aa";
    eq_str (filter a "abca") "aa";
    eq_str (filter a "baca") "aa";
    eq_str (filter a "bcaa") "aa";
  in
  gen_filter String.filter (fun c -> c = 'a');
  gen_filter String.filter_map (fun c -> if c = 'a' then Some c else None);
  let subst_a = function 'a' -> Some 'z' | c -> Some c in
  no_alloc String.filter_map subst_a "";
  no_alloc String.filter_map subst_a "b";
  no_alloc String.filter_map subst_a "bcd";
  eq_str (String.filter_map subst_a "a") "z";
  eq_str (String.filter_map subst_a "aa") "zz";
  eq_str (String.filter_map subst_a "aaa") "zzz";
  eq_str (String.filter_map subst_a "ab") "zb";
  eq_str (String.filter_map subst_a "ba") "bz";
  eq_str (String.filter_map subst_a "abc") "zbc";
  eq_str (String.filter_map subst_a "bac") "bzc";
  eq_str (String.filter_map subst_a "bca") "bcz";
  eq_str (String.filter_map subst_a "aba") "zbz";
  eq_str (String.filter_map subst_a "aab") "zzb";
  eq_str (String.filter_map subst_a "baa") "bzz";
  eq_str (String.filter_map subst_a "aabc") "zzbc";
  eq_str (String.filter_map subst_a "abac") "zbzc";
  eq_str (String.filter_map subst_a "abca") "zbcz";
  eq_str (String.filter_map subst_a "baca") "bzcz";
  eq_str (String.filter_map subst_a "bcaa") "bczz";
  let subst_a_del_b = function 'a' -> Some 'z' | 'b' -> None | c -> Some c in
  no_alloc String.filter_map subst_a_del_b "";
  no_alloc String.filter_map subst_a_del_b "c";
  no_alloc String.filter_map subst_a_del_b "cd";
  eq_str (String.filter_map subst_a_del_b "a") "z";
  eq_str (String.filter_map subst_a_del_b "aa") "zz";
  eq_str (String.filter_map subst_a_del_b "aaa") "zzz";
  eq_str (String.filter_map subst_a_del_b "ab") "z";
  eq_str (String.filter_map subst_a_del_b "ba") "z";
  eq_str (String.filter_map subst_a_del_b "abc") "zc";
  eq_str (String.filter_map subst_a_del_b "bac") "zc";
  eq_str (String.filter_map subst_a_del_b "bca") "cz";
  eq_str (String.filter_map subst_a_del_b "aba") "zz";
  eq_str (String.filter_map subst_a_del_b "aab") "zz";
  eq_str (String.filter_map subst_a_del_b "baa") "zz";
  eq_str (String.filter_map subst_a_del_b "aabc") "zzc";
  eq_str (String.filter_map subst_a_del_b "abac") "zzc";
  eq_str (String.filter_map subst_a_del_b "abca") "zcz";
  eq_str (String.filter_map subst_a_del_b "baca") "zcz";
  eq_str (String.filter_map subst_a_del_b "bcaa") "czz";
  ()

let map = test "String.map[i]" @@ fun () ->
  let next_letter c = Char.(of_byte @@ to_int c + 1) in
  let no_alloc map f s = eq_bool (map f s == s) true in
  no_alloc String.map (fun c -> c) String.empty;
  no_alloc String.map (fun c -> c) "abcd";
  eq_str (String.map (fun c -> fail "invoked"; c) "") "";
  eq_str (String.map next_letter "abcd") "bcde";
  no_alloc String.mapi (fun _ c -> c) String.empty;
  no_alloc String.mapi (fun _ c -> c) "abcd";
  eq_str (String.mapi (fun _ c -> fail "invoked"; c) "") "";
  eq_str (String.mapi (fun i c -> Char.(of_byte @@ to_int c + i)) "abcd")
    "aceg";
  ()

let fold = test "String.fold_{left,right}" @@ fun () ->
  let eql = eq_list ~eq:(=) ~pp:pp_char in
  String.fold_left (fun _ _ -> fail "invoked") () "";
  eql (String.fold_left (fun acc c -> c :: acc) [] "") [];
  eql (String.fold_left (fun acc c -> c :: acc) [] "abc") ['c';'b';'a'];
  String.fold_right (fun _ _ -> fail "invoked") "" ();
  eql (String.fold_right (fun c acc -> c :: acc) "" []) [];
  eql (String.fold_right (fun c acc -> c :: acc) "abc" []) ['a';'b';'c'];
  ()

let iter = test "String.iter[i]" @@ fun () ->
  let s = "abcd" in
  String.iter (fun _ -> fail "invoked") "";
  String.iteri (fun _ _ -> fail "invoked") "";
  (let i = ref 0 in String.iter (fun c -> eq_char s.[!i] c; incr i) s);
  String.iteri (fun i c -> eq_char s.[i] c) s;
  ()

(* Ascii support *)

let ascii_is_valid = test "String.Ascii.is_valid" @@ fun () ->
  eq_bool (String.Ascii.is_valid "") true;
  eq_bool (String.Ascii.is_valid "a") true;
  eq_bool (String.(Ascii.is_valid (v ~len:(0x7F + 1)
                                     (fun i -> Char.of_byte i)))) true;
  ()

let ascii_casing =
  test "String.Ascii.{uppercase,lowercase,capitalize,uncapitalize}"
  @@ fun () ->
  let no_alloc f s = eq_bool (f s == s) true in
  no_alloc String.Ascii.uppercase "";
  no_alloc String.Ascii.uppercase "HEHEY \x7F\xFF\x00\x0A";
  eq_str (String.Ascii.uppercase "HeHey \x7F\xFF\x00\x0A")
    "HEHEY \x7F\xFF\x00\x0A";
  eq_str (String.Ascii.uppercase "abcdefghijklmnopqrstuvwxyz")
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  no_alloc String.Ascii.lowercase "";
  no_alloc String.Ascii.lowercase "hehey \x7F\xFF\x00\x0A";
  eq_str (String.Ascii.lowercase "hEhEY \x7F\xFF\x00\x0A")
    "hehey \x7F\xFF\x00\x0A";
  eq_str (String.Ascii.lowercase "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    "abcdefghijklmnopqrstuvwxyz";
  no_alloc String.Ascii.capitalize "";
  no_alloc String.Ascii.capitalize "Hehey";
  no_alloc String.Ascii.capitalize "\x00hehey";
  eq_str (String.Ascii.capitalize "hehey") "Hehey";
  no_alloc String.Ascii.uncapitalize "";
  no_alloc String.Ascii.uncapitalize "hehey";
  no_alloc String.Ascii.uncapitalize "\x00hehey";
  eq_str (String.Ascii.uncapitalize "Hehey") "hehey";
  ()

let ascii_escapes = test "String.Ascii.escape[_string]" @@ fun () ->
  let no_alloc s = eq_bool ((String.Ascii.escape s) == s) true in
  no_alloc "";
  no_alloc "abcd";
  no_alloc "~";
  no_alloc " ";
  eq_str (String.Ascii.escape "\x00abc") "\\x00abc";
  eq_str (String.Ascii.escape "\nabc") "\\x0Aabc";
  eq_str (String.Ascii.escape "\nab\xFFc") "\\x0Aab\\xFFc";
  eq_str (String.Ascii.escape "\nab\xFF") "\\x0Aab\\xFF";
  eq_str (String.Ascii.escape "\nab\\") "\\x0Aab\\\\";
  eq_str (String.Ascii.escape "\\") "\\\\";
  eq_str (String.Ascii.escape "\\\x00\x1F\x7F\xFF") "\\\\\\x00\\x1F\\x7F\\xFF";
  let no_alloc s =
    eq_bool ((String.Ascii.escape_string s) == s) true
  in
  no_alloc "";
  no_alloc "abcd";
  no_alloc "~";
  no_alloc " ";
  eq_str (String.Ascii.escape_string "\x00abc") "\\x00abc";
  eq_str (String.Ascii.escape_string "\nabc") "\\nabc";
  eq_str (String.Ascii.escape_string "\nab\xFFc") "\\nab\\xFFc";
  eq_str (String.Ascii.escape_string "\nab\xFF") "\\nab\\xFF";
  eq_str (String.Ascii.escape_string "\nab\\") "\\nab\\\\";
  eq_str (String.Ascii.escape_string "\\") "\\\\";
  eq_str (String.Ascii.escape_string "\b\t\n\r\"\\\x00\x1F\x7F\xFF")
    "\\b\\t\\n\\r\\\"\\\\\\x00\\x1F\\x7F\\xFF";
  ()

let ascii_unescapes = test "String.Ascii.unescape[_string]" @@ fun () ->
  let no_alloc unescape s = match unescape s with
  | None -> fail "expected (Some %S)" s
  | Some s' -> eq_bool (s == s') true
  in
  let eq_o = eq_option ~eq:String.equal ~pp:pp_str in
  no_alloc String.Ascii.unescape "";
  no_alloc String.Ascii.unescape "abcd";
  no_alloc String.Ascii.unescape "~";
  no_alloc String.Ascii.unescape " ";
  eq_o (String.Ascii.unescape "\\x00abc") (Some "\x00abc");
  eq_o (String.Ascii.unescape "\\x0Aabc") (Some "\nabc");
  eq_o (String.Ascii.unescape "\\x0Aab\\xFFc") (Some "\nab\xFFc");
  eq_o (String.Ascii.unescape "\\x0Aab\\xFF") (Some "\nab\xFF");
  eq_o (String.Ascii.unescape "\\x0Aab\\\\") (Some "\nab\\");
  eq_o (String.Ascii.unescape "a\\\\") (Some "a\\");
  eq_o (String.Ascii.unescape "\\\\") (Some "\\");
  eq_o (String.Ascii.unescape "a\\\\\\x00\\x1F\\x7F\\xFF")
    (Some "a\\\x00\x1F\x7F\xFF");
  eq_o (String.Ascii.unescape "\\x61") (Some "a");
  eq_o (String.Ascii.unescape "\\x20") (Some " ");
  eq_o (String.Ascii.unescape "\\x2") None;
  eq_o (String.Ascii.unescape "\\x") None;
  eq_o (String.Ascii.unescape "\\") None;
  eq_o (String.Ascii.unescape "a\\b") None;
  eq_o (String.Ascii.unescape "a\\t") None;
  eq_o (String.Ascii.unescape "b\\n") None;
  eq_o (String.Ascii.unescape "b\\r") None;
  eq_o (String.Ascii.unescape "b\\\"") None;
  eq_o (String.Ascii.unescape "b\\z") None;
  eq_o (String.Ascii.unescape "b\\1") None;
  no_alloc String.Ascii.unescape_string "";
  no_alloc String.Ascii.unescape_string "abcd";
  no_alloc String.Ascii.unescape_string "~";
  no_alloc String.Ascii.unescape_string " ";
  eq_o (String.Ascii.unescape_string "\\x00abc") (Some "\x00abc");
  eq_o (String.Ascii.unescape_string "\\nabc") (Some "\nabc");
  eq_o (String.Ascii.unescape_string "\\nab\\xFFc") (Some "\nab\xFFc");
  eq_o (String.Ascii.unescape_string "\\nab\\xFF") (Some "\nab\xFF");
  eq_o (String.Ascii.unescape_string "\\nab\\\\") (Some "\nab\\");
  eq_o (String.Ascii.unescape_string "a\\\\") (Some "a\\");
  eq_o (String.Ascii.unescape_string "\\\\") (Some "\\");
  eq_o (String.Ascii.unescape_string
          "\\b\\t\\n\\r\\\"\\\\\\x00\\x1F\\x7F\\xFF")
    (Some "\b\t\n\r\"\\\x00\x1F\x7F\xFF");
  eq_o (String.Ascii.unescape_string "\\x61") (Some "a");
  eq_o (String.Ascii.unescape_string "\\x20") (Some " ");
  eq_o (String.Ascii.unescape_string "\\x2") None;
  eq_o (String.Ascii.unescape_string "\\x") None;
  eq_o (String.Ascii.unescape_string "\\") None;
  eq_o (String.Ascii.unescape_string "a\\b") (Some "a\b");
  eq_o (String.Ascii.unescape_string "a\\t") (Some "a\t");
  eq_o (String.Ascii.unescape_string "b\\n") (Some "b\n");
  eq_o (String.Ascii.unescape_string "b\\r") (Some "b\r");
  eq_o (String.Ascii.unescape_string "b\\\"") (Some "b\"");
  eq_o (String.Ascii.unescape_string "b\\\'") (Some "b'");
  eq_o (String.Ascii.unescape_string "b\\z") None;
  eq_o (String.Ascii.unescape_string "b\\1") None;
  ()

(* Uniqueness *)

let uniquify = test "String.uniquify" @@ fun () ->
  let eq = eq_list ~eq:(=) ~pp:pp_str in
  eq (String.uniquify []) [];
  eq (String.uniquify ["a";"b";"c"]) ["a";"b";"c"];
  eq (String.uniquify ["a";"a";"b";"c"]) ["a";"b";"c"];
  eq (String.uniquify ["a";"b";"a";"c"]) ["a";"b";"c"];
  eq (String.uniquify ["a";"b";"c";"a"]) ["a";"b";"c"];
  eq (String.uniquify ["b";"a";"b";"c"]) ["b";"a";"c"];
  eq (String.uniquify ["a";"b";"b";"c"]) ["a";"b";"c"];
  eq (String.uniquify ["a";"b";"c";"b"]) ["a";"b";"c"];
  ()

let suite = suite "String functions"
    [ misc;
      head;
      append;
      concat;
      is_empty;
      is_prefix;
      is_infix;
      is_suffix;
      for_all;
      exists;
      equal;
      compare;
      with_range;
      with_index_range;
      trim;
      span;
      cut;
      cuts;
      fields;
      find;
      find_sub;
      filter;
      map;
      iter;
      fold;
      ascii_is_valid;
      ascii_casing;
      ascii_escapes;
      ascii_unescapes;
      uniquify; ]

(*---------------------------------------------------------------------------
   Copyright (c) 2015 The astring programmers

   Permission to use, copy, modify, and/or distribute this software for any
   purpose with or without fee is hereby granted, provided that the above
   copyright notice and this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ---------------------------------------------------------------------------*)
