123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195open!Coreopen!ImportopenProcopenBonsai.For_openmoduleOne_at_a_time=Bonsai_extra.One_at_a_time(* These test are a bit hairier then I'd like because [One_at_a_time.effect]
only updates its state machine on [Handle.show] boundaries, which makes
things take longer than I'd like. While we usually try to avoid computation
introducing frame-delays, this seems sort of okay because it isn't late by a
whole frame, and the lateness doesn't compound when this computation is
chained together with itself. *)letcreate_handlecomponent=Handle.create(modulestructtypet=(int->intOne_at_a_time.Response.tEffect.t)*One_at_a_time.Status.ttypeincoming=intletview(_,status)=Sexp.to_string([%sexp_of:One_at_a_time.Status.t]status)letincoming(f,_)i=let%bind.Effectresult=fiinEffect.print_s[%message(result:intOne_at_a_time.Response.t)];;end)component;;let%expect_test{| One_at_a_time.effect only runs one instance of an effect at a time |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondi=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respondi)inletcomponent=One_at_a_time.effect(Value.return(Effect.For_testing.of_query_response_trackerqrt))inlethandle=create_handlecomponentinHandle.showhandle;[%expect{| Idle |}];Handle.do_actionshandle[0];Handle.showhandle;[%expect{| Busy |}];Handle.do_actionshandle[0];Handle.showhandle;[%expect{|
(result Busy)
Busy |}];respond0;Handle.showhandle;[%expect{|
(result (Result 0))
Idle |}];Handle.do_actionshandle[0];Handle.showhandle;[%expect{| Busy |}];respond1;Handle.do_actionshandle[0];Handle.showhandle;[%expect{|
(result (Result 1))
Busy |}];respond2;Handle.do_actionshandle[0;0];Handle.showhandle;[%expect{|
(result (Result 2))
(result Busy)
Busy |}];respond3;respond4;Handle.showhandle;[%expect{|
(result (Result 3))
Idle |}];;let%expect_test{| Double [One_at_a_time.effect] application should be consistent between the two invocations. |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletrespondi=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun()->Respondi)inletcomponent=letopenBonsai.Let_syntaxinlet%subeffect,status1=One_at_a_time.effect(Value.return(Effect.For_testing.of_query_response_trackerqrt))inlet%subeffect,status2=One_at_a_time.effecteffectinlet%arrstatus1=status1andstatus2=status2andeffect=effectineffect,status1,status2inlethandle=Handle.create(modulestructtypet=(unit->intOne_at_a_time.Response.tOne_at_a_time.Response.tUi_effect.t)*One_at_a_time.Status.t*One_at_a_time.Status.ttypeincoming=unitletview(_,status1,status2)=Sexp.to_string_hum[%message(status1:One_at_a_time.Status.t)(status2:One_at_a_time.Status.t)];;letincoming(effect,_,_)()=let%bind.Effectresult=effect()inEffect.print_s[%message(result:intOne_at_a_time.Response.tOne_at_a_time.Response.t)];;end)componentinHandle.showhandle;[%expect{| ((status1 Idle) (status2 Idle)) |}];Handle.do_actionshandle[()];Handle.showhandle;[%expect{|
((status1 Busy) (status2 Busy)) |}];Handle.do_actionshandle[()];Handle.showhandle;[%expect{|
(result Busy)
((status1 Busy) (status2 Busy)) |}];respond0;Handle.showhandle;[%expect{|
(result (Result (Result 0)))
((status1 Idle) (status2 Idle)) |}];Handle.do_actionshandle[()];Handle.showhandle;[%expect{|
((status1 Busy) (status2 Busy)) |}];respond1;Handle.do_actionshandle[()];Handle.showhandle;[%expect{|
(result Busy)
(result (Result (Result 1)))
((status1 Idle) (status2 Idle)) |}];Handle.do_actionshandle[();()];respond2;Handle.showhandle;[%expect{|
(result Busy)
((status1 Busy) (status2 Busy)) |}];respond3;respond4;Handle.showhandle;[%expect{|
(result (Result (Result 3)))
((status1 Idle) (status2 Idle)) |}];;let%expect_test{| One_at_a_time.effect releases lock after effect throws exception |}=letqrt=Effect.For_testing.Query_response_tracker.create()inletcomplete()=Effect.For_testing.Query_response_tracker.maybe_respondqrt~f:(fun_->Respond())inletdelay_effect=Effect.For_testing.of_query_response_trackerqrtinletfail_effectx=ifx=0thenfailwith"error while computing effect"else(let%bind.Effect()=delay_effect()inEffect.of_sync_fun(funx->ifx=1thenfailwith"error while running effect"elsex)x)inletcomponent=One_at_a_time.effect(Value.returnfail_effect)inlethandle=create_handlecomponentinHandle.showhandle;[%expect{| Idle |}];Handle.do_actionshandle[0];Expect_test_helpers_core.require_does_raise[%here](fun()->Handle.showhandle);[%expect{| (Failure "error while computing effect") |}];Handle.showhandle;[%expect{| Idle |}];Handle.do_actionshandle[1];Handle.showhandle;[%expect{| Busy |}];Expect_test_helpers_core.require_does_raise[%here](fun()->complete());[%expect{|
(Failure "error while running effect") |}];Handle.showhandle;[%expect{| Busy |}];;