(* Lightweight thread library for Objective Caml
 * http://www.ocsigen.org/lwt
 * Module Test_lwt
 * Copyright (C) 2010 Jérémie Dimino, Pierre Chambart
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, with linking exceptions;
 * either version 2.1 of the License, or (at your option) any later
 * version. See COPYING file for details.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *)

open Test
open Lwt

let ( <=> ) v v' =
  assert ( state v = v')

let test_exn f v e =
  assert (try f v; false with exn -> exn = e)

let f x = return ("test"^x)
let g x = ("test"^x)

exception Exn

let key : int key = new_key ()

let with_async_exception_hook hook f =
  let save = !Lwt.async_exception_hook in
  Lwt.async_exception_hook := hook;
  try
    let x = f () in
    Lwt.async_exception_hook := save;
    x
  with exn ->
    Lwt.async_exception_hook := save;
    raise exn

let suite = suite "lwt" [
  test "0"
    (fun () ->
       return "test" <=> Return "test";
       fail Exn <=> Fail Exn;
       bind (return "test") f <=> Return "testtest";
       bind (fail Exn) return <=> Fail Exn;
       (return "test") >>= f <=> Return "testtest";
       f =<< (return "test") <=> Return "testtest";
       map g (return "test") <=> Return "testtest";
       (return "test") >|= g <=> Return "testtest";
       g =|< (return "test") <=> Return "testtest";
       return true);

  test "1"
    (fun () ->
       catch return (fun e -> return ()) <=> Return ();
       catch (fun () -> fail Exn) (function Exn -> return ()| e -> assert false) <=> Return ();
       catch (fun () -> fail Exn) (fun e -> fail e) <=> Fail Exn;
       return true);

  test "2"
    (fun () ->
       try_bind return return ( fun e -> assert false ) <=> Return ();
       try_bind (fun () -> fail Exn) return (function Exn -> return ()| e -> assert false) <=> Return ();
       return true);

  test "3"
    (fun () ->
       finalize return return <=> Return ();
       finalize (fun () -> fail Exn) return <=> Fail Exn;
       return true);

  test "4"
    (fun () ->
       apply (fun () -> raise Exn) () <=> Fail Exn;
       return true);

  test "5"
    (fun () ->
       choose [return ()] <=> Return ();
       return () <?> return () <=> Return ();
       return true);

  test "6"
    (fun () ->
       join [return ()] <=> Return ();
       return () <&> return () <=> Return ();
       return true);

  test "7"
    (fun () ->
       assert (ignore_result (return ()) = ());
       test_exn ignore_result (fail Exn) Exn;
       return true);

  test "8"
    (fun () ->
       let t,w = wait () in
       t <=> Sleep;
       wakeup w ();
       t <=> Return ();
       return true);

  test "9"
    (fun () ->
       let t,w = wait () in
       wakeup_exn w Exn;
       t <=> Fail Exn;
       return true);

  test "10"
    (fun () ->
       let t,w = task () in
       t <=> Sleep;
       wakeup w ();
       t <=> Return ();
       return true);

  test "11"
    (fun () ->
       let t,w = wait () in
       let r1 = choose [t] in r1 <=> Sleep;
       choose [t;return ()] <=> Return ();
       join [fail Exn;t] <=> Sleep;
       let r2 = join [t] in r2 <=> Sleep;
       let r3 = join [t;return ()] in r3 <=> Sleep;
       wakeup w ();
       r1 <=> Return (); r2 <=> Return (); r3 <=> Return ();
       return true);

  test "12"
    (fun () ->
       let t,w = wait () in
       let t',w' = wait () in
       let r1 = join [return ();t] in
       let r2 = join [t;t'] in
       wakeup_exn w Exn;
       r1 <=> Fail Exn;
       r2 <=> Sleep;
       return true);

  test "13"
    (fun () ->
       let t,w = wait () in
       let t',w' = wait () in
       let r = bind (choose [t;t']) return in
       r <=> Sleep;
       wakeup w' ();
       r <=> Return ();
       let r' = bind (choose [t;t]) return in
       wakeup w ();
       r' <=> Return ();
       return true);

  test "14"
    (fun () ->
       assert ( poll (return ()) = Some () );
       test_exn poll (fail Exn) Exn;
       let t,w = wait () in
       assert ( poll t = None );
       return true);

  test_direct "15.1"
    (fun () ->
       let exns = ref [] in
       with_async_exception_hook
         (fun e -> exns := e :: !exns)
         (fun () ->
            let t, w = wait () in
            ignore_result t;
            wakeup w ();
            assert (!exns = []);
            let t, w = wait () in
            ignore_result t;
            wakeup_exn w Exn;
            assert (!exns = [Exn]);
            true));

  test "16"
    (fun () ->
       let t,w = wait () in
       let r1 = catch (fun () -> t) (fun e -> return ()) in r1 <=> Sleep;
       let r2 = try_bind (fun () -> t) return ( fun e -> assert false ) in r2 <=> Sleep;
       wakeup w ();
       r1 <=> Return ();
       r2 <=> Return ();
       return true);

  (****)

  test "17"
    (fun () ->
       let t,w = task () in
       let t',w' = wait () in
       let t'' = return () in
       cancel t;
       cancel t';
       cancel t'';
       t <=> Fail Canceled;
       t' <=> Sleep;
       t'' <=> Return ()  ;
       return true);

  test "18"
    (fun () ->
       let t,w = task () in
       let r = bind t return in
       cancel r;
       r <=> Fail Canceled;
       return true);

  test "19"
    (fun () ->
       let t,w = task () in
       on_cancel t (fun () -> ());
       on_cancel (return ()) (fun () -> assert false);
       cancel t;
       on_cancel t (fun () -> ());
       let t,w = wait () in
       on_cancel t (fun () -> ());
       wakeup w ();
       return true);

  test "20"
    (fun () ->
       let t,w = task () in
       let t',w' = wait () in
       let r = pick [t;t'] in r <=> Sleep;
       wakeup w' ();
       r <=> Return ();
       t <=> Fail Canceled;
       return true);

  test "21"
    (fun () ->
       pick [return ()] <=> Return ();
       return true);

  test "22"
    (fun () ->
       let t,w = task () in
       let t',w' = wait () in
       let r = pick [t;t'] in
       cancel r;
       r <=> Fail Canceled;
       t <=> Fail Canceled;
       return true);

  test "23"
    (fun () ->
       let t,w = task () in
       let r = join [t] in
       cancel r;
       r <=> Fail Canceled;
       t <=> Fail Canceled;
       return true);

  test "24"
    (fun () ->
       let t,w = task () in
       let r = choose [t] in
       cancel r;
       r <=> Fail Canceled;
       t <=> Fail Canceled;
       return true);

  test "25"
    (fun () ->
       let t,w = task () in
       let r = catch (fun () -> t) (function Canceled -> return ()| _ -> assert false) in
       cancel r;
       r <=> Return ();
       t <=> Fail Canceled;
       return true);

  test "26"
    (fun () ->
       let t,w = task () in
       let r = try_bind (fun () -> t) (fun _ -> assert false) (function Canceled -> return ()| _ -> assert false) in
       cancel r;
       r <=> Return ();
       t <=> Fail Canceled;
       return true);

  test "27"
    (fun () ->
       let t,w = wait () in
       wakeup w ();
       test_exn (wakeup w) () (Invalid_argument "Lwt.wakeup_result");
       return true);

  test "28"
    (fun () ->
       let t,w = task () in
       cancel t;
       wakeup w ();
       return true);

  test "29"
    (fun () ->
       let t,w = wait () in
       let t',w' = wait () in
       let r = bind t ( fun () -> t' ) in
       let r' = bind t ( fun () -> r ) in
       wakeup w ();
       r <=> Sleep;
       r' <=> Sleep;
       wakeup w' ();
       r <=> Return ();
       r' <=> Return ();
       return true);

  test "30"
    (fun () ->
       let t,w = wait () in
       let t',w' = wait () in
       let t'',w'' = wait () in
       let r = bind t ( fun () -> t' ) in
       let r' = bind t'' ( fun () -> r ) in
       wakeup w'' ();
       r <=> Sleep;
       r' <=> Sleep;
       wakeup w ();
       wakeup w' ();
       r' <=> Return ();
       r <=> Return ();
       return true);

  test "31"
    (fun () ->
       let t,w = wait () in
       let a = ref (return ()) in
       let r = bind t ( fun () -> !a ) in
       a := r;
       wakeup w ();
       return true);

  test "choose"
    (fun () ->
       let t1,w1 = wait () in
       let t2,w2 = wait () in
       let rec f = function
	 | 0 -> []
	 | i -> (choose [t1;t2])::(f (i-1))
       in
       let l = f 100 in
       t1 <=> Sleep;
       t2 <=> Sleep;
       List.iter (fun t -> t <=> Sleep) l;
       wakeup w1 ();
       List.iter (fun t -> t <=> Return ()) l;
       t1 <=> Return ();
       t2 <=> Sleep;
       return true);

  test "protected return"
    (fun () ->
       let t = return 1 in
       let t' = protected t in
       return ((state t' = Return 1) && (state t = Return 1)));

  test "protected fail"
    (fun () ->
       let t = fail Exn in
       let t' = protected t in
       return ((state t' = Fail Exn) && (state t = Fail Exn)));

  test "protected wait 1"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       wakeup w 1;
       return ((state t' = Return 1) && (state t = Return 1)));

  test "protected wait 2"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       wakeup_exn w Exn;
       return ((state t' = Fail Exn) && (state t = Fail Exn)));

  test "protected wait 3"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       cancel t';
       return ((state t' = Fail Canceled) && (state t = Sleep)));

  test "protected wait 4"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       cancel t';
       wakeup w 1;
       return ((state t' = Fail Canceled) && (state t = Return 1)));

  test "protected wait 5"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       cancel t';
       wakeup_exn w Exn;
       return ((state t' = Fail Canceled) && (state t = Fail Exn)));

  test "protected wait 6"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       wakeup_exn w Exn;
       cancel t';
       return ((state t' = Fail Exn) && (state t = Fail Exn)));

  test "protected wait 7"
    (fun () ->
       let t,w = wait () in
       let t' = protected t in
       wakeup w 1;
       cancel t';
       return ((state t' = Return 1) && (state t = Return 1)));

  test "join 1"
    (fun () ->
       let t1 = fail Exn in
       let t2 = join [t1] in
       return ((state t1 = Fail Exn) && (state t2 = Fail Exn)));

  test "join 2"
    (fun () ->
       let t1,w1 = wait () in
       let t2 = join [t1] in
       wakeup_exn w1 Exn;
       return ((state t1 = Fail Exn) && (state t2 = Fail Exn)));

  test "join 3"
    (fun () ->
       let t1 = fail Exn in
       let t2,w2 = wait () in
       let t3 = fail Not_found in
       let t4 = join [t2;t1;t3] in
       return ((state t1 = Fail Exn) && (state t2 = Sleep) &&
		 (state t3 = Fail Not_found) && (state t4 = Sleep)));

  test "join 4"
    (fun () ->
       let t1 = fail Exn in
       let t2,w2 = wait () in
       let t3 = return () in
       let rec f = function
	 | 0 -> return true
	 | i ->
	     let t = join [t2;t3;t1] in
	     if ((state t1 = Fail Exn) && (state t2 = Sleep)
		 && (state t = Sleep) && (state t3 = Return ()))
	     then f (i-1)
	     else return false
       in
       f 100);

  test "cancel loop"
    (fun () ->
       let rec loop () =
         lwt () = Lwt_unix.yield () in
         loop ()
       in
       let t = loop () in
       cancel t;
       return (state t = Fail Canceled));

  test "cancel loop 2"
    (fun () ->
       let rec loop () =
         lwt () = Lwt_unix.yield () in
         loop ()
       in
       let t = loop () in
       lwt () = Lwt_unix.yield () in
       cancel t;
       return (state t = Fail Canceled));

  test "nchoose"
    (fun () ->
       lwt l = nchoose [return 1; return 2] in
       return (l = [1; 2]));

  test "npick"
    (fun () ->
       lwt l = npick [return 1; return 2] in
       return (l = [1; 2]));

  test "bind/cancel 1"
    (fun () ->
       let waiter, wakener = wait () in
       let t =
         lwt () = waiter in
         let waiter, wakener = task () in
         waiter
       in
       wakeup wakener ();
       cancel t;
       return (state t = Fail Canceled));

  test "bind/cancel 2"
    (fun () ->
       let waiter, wakener = wait () in
       let t =
         lwt () = waiter in
         let waiter, wakener = task () in
         waiter
       in
       let t = t >>= return in
       wakeup wakener ();
       cancel t;
       return (state t = Fail Canceled));

  test "bind/cancel 3"
    (fun () ->
       let waiter1, wakener1 = wait () in
       let waiter2, wakener2 = wait () in
       let t =
         lwt () = waiter1 in
         try_lwt
           lwt () = waiter2 in
           fst (task ())
         with Canceled ->
           return true
       in
       wakeup wakener1 ();
       wakeup wakener2 ();
       cancel t;
       return (state t = Return true));

  test "data 1"
    (fun () ->
       with_value key (Some 1)
         (fun () -> return (get key = Some 1)));

  test "data 2"
    (fun () ->
       with_value key (Some 1)
         (fun () ->
            with_value key (Some 2)
              (fun () -> return (get key = Some 2))));

  test "data 3"
    (fun () ->
       with_value key (Some 1)
         (fun () ->
            let waiter, wakener = wait () in
            let t =
              with_value key (Some 2)
                (fun () ->
                   lwt () = waiter in
                   return (get key = Some 2))
            in
            wakeup wakener ();
            t));


  test "on_cancel race condition"
    (fun () ->
       (* Queue of cancel-able pending threads. *)
       let queue = Lwt_sequence.create () in
       (* Add two cancel-able pending threads to the queue. *)
       let waiter1, wakener1 = task () in
       let node1 = Lwt_sequence.add_r wakener1 queue in
       let waiter2, wakener2 = task () in
       let node2 = Lwt_sequence.add_r wakener2 queue in
       (* Remove nodes when a thread is canceled. *)
       on_cancel waiter1 (fun () -> Lwt_sequence.remove node1);
       on_cancel waiter2 (fun () -> Lwt_sequence.remove node2);
       (* Add another one to the left of the on_cancel one: *)
       let waiter', wakener' = wait () in
       let t = bind waiter' (fun _ -> waiter1) in
       (* Send a value to the first thread of the queue when [t]
          fails. *)
       ignore (
         try_lwt
           t
         with _ ->
           (* Take the first thread from the queue and send it a value. *)
           wakeup (Lwt_sequence.take_l queue) 42;
           return 0
       );
       (* Terminate [waiter'] so [waiter1 <- Repr t] *)
       wakeup wakener' 0;
       (* now there are two thunk functions on [t]:
          - (fun _ -> wakeup (Lwt_sequence.take_l queue) 42; return 0);
          - (fun _ -> Lwt_sequence.remove node); *)
       (* Cancel [waiter1]. If on_cancel handlers are not executed
          before other thunk functions, [42] is lost. *)
       cancel waiter1;
       return (state waiter1 = Fail Canceled && state waiter2 = Return 42));

  test "re-cancel"
    (fun () ->
       let waiter1, wakener1 = task () in
       let waiter2, wakener2 = task () in
       let waiter3, wakener3 = task () in
       let t1 = catch (fun () -> waiter1) (fun exn -> waiter2) in
       let t2 = bind t1 return in
       let t3 = bind waiter3 (fun () -> t1) in
       wakeup wakener3 ();
       cancel t3;
       cancel t2;
       return (List.for_all (fun t -> state t = Fail Canceled) [t1; t2; t3; waiter1; waiter2]));

  test "re-cancel choose"
    (fun () ->
       let waiter1, wakener1 = task () in
       let waiter2, wakener2 = task () in
       let t1 = catch (fun () -> waiter1) (fun exn -> waiter2) in
       let t2 = choose [t1] in
       cancel t2;
       cancel t2;
       return (state waiter1 = Fail Canceled && state waiter2 = Fail Canceled));

  test "ignore_result 2"
    (fun () ->
       let exns = ref [] in
       with_async_exception_hook
         (fun e -> exns := e :: !exns)
         (fun () ->
            let waiter, wakener = wait () in
            let t1 = map (fun () -> 42) waiter in
            ignore_result (
              lwt () = waiter in
              fail Exit
            );
            let t2 = map (fun () -> "42") waiter in
            wakeup wakener ();
            return (!exns = [Exit] && state t1 = Return 42 && state t2 = Return "42")));

  test "on_success exn 2"
    (fun () ->
       let exns = ref [] in
       with_async_exception_hook
         (fun e -> exns := e :: !exns)
         (fun () ->
            let waiter, wakener = wait () in
            let t1 = map (fun () -> 42) waiter in
            on_success waiter (fun () -> raise Exit);
            let t2 = map (fun () -> "42") waiter in
            wakeup wakener ();
            return (!exns = [Exit] && state t1 = Return 42 && state t2 = Return "42")));
]
