123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288(**************************************************************************)(* The OUnit library *)(* *)(* Copyright (C) 2002-2008 Maas-Maarten Zeeman. *)(* Copyright (C) 2010 OCamlCore SARL *)(* Copyright (C) 2013 Sylvain Le Gall *)(* *)(* The package OUnit is copyright by Maas-Maarten Zeeman, OCamlCore SARL *)(* and Sylvain Le Gall. *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining *)(* a copy of this document and the OUnit software ("the Software"), to *)(* deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, *)(* sublicense, and/or sell copies of the Software, and to permit persons *)(* to whom the Software is furnished to do so, subject to the following *)(* conditions: *)(* *)(* The above copyright notice and this permission notice shall be *)(* included in all copies or substantial portions of the Software. *)(* *)(* The Software is provided ``as is'', without warranty of any kind, *)(* express or implied, including but not limited to the warranties of *)(* merchantability, fitness for a particular purpose and noninfringement. *)(* In no event shall Maas-Maarten Zeeman be liable for any claim, damages *)(* or other liability, whether in an action of contract, tort or *)(* otherwise, arising from, out of or in connection with the Software or *)(* the use or other dealings in the software. *)(* *)(* See LICENSE.txt for details. *)(**************************************************************************)(** Manipulate the state of OUnit runner.
*)openOUnitLoggeropenOUnitTestopenOUnitChoosertype'workernext_test_case_t=|Not_enough_worker|Try_again|Next_test_caseofpath*test_fun*'worker|Finishedtypetime=floattype'workertest_running={test_length:test_length;deadline:time;next_health_check:time;worker:'worker;}type'workert={tests_planned:(path*(test_length*test_fun))list;tests_running:(path*('workertest_running))list;tests_passed:(OUnitTest.result_full*OUnitTest.result_list)list;idle_workers:'workerlist;chooser:OUnitChooser.chooser;cache:OUnitCache.cache;health_check_interval:time;}lethealth_check_interval=OUnitConf.make_float"health_check_interval"1.0"Seconds between checking health of workers."letcreateconfchoosertest_cases={tests_passed=[];tests_planned=List.map(fun(test_path,test_length,test_fun)->test_path,(test_length,test_fun))test_cases;tests_running=[];idle_workers=[];chooser=chooser;cache=OUnitCache.loadconf;health_check_interval=health_check_intervalconf;}letfilter_outelst=List.filter(fun(e',_)->e<>e')lstletmaybe_dump_cacheconfstate=ifstate.tests_running=[]&&state.tests_planned=[]then(* We are finished, all results are in, flush the cache. *)OUnitCache.dumpconf(List.fold_left(funcache(path,test_result,_)->OUnitCache.add_resultpathtest_resultcache)state.cache(List.mapfststate.tests_passed));stateletadd_test_resultsconfall_test_resultsstate=let((test_path,_,_),_)=all_test_resultsinletstate={statewithtests_passed=all_test_results::state.tests_passed;tests_planned=filter_outtest_pathstate.tests_planned};inmaybe_dump_cacheconfstatelettest_finishedconfall_test_resultsworkerstate=let((test_path,_,_),_)=all_test_resultsinletstate={(add_test_resultsconfall_test_resultsstate)withtests_running=filter_outtest_pathstate.tests_running;idle_workers=worker::state.idle_workers}inmaybe_dump_cacheconfstateletadd_workerworkerstate={statewithidle_workers=worker::state.idle_workers}letremove_idle_workerworkerstate=letfound,idle_workers=List.fold_left(fun(found,lst)worker'->ifworker'==workerthentrue,lstelsefound,worker'::lst)(false,[])state.idle_workersinifnotfoundthenraiseNot_found;{statewithidle_workers=idle_workers}letcount_workerstate=List.lengthstate.idle_workers+List.lengthstate.tests_runningletcount_tests_runningstate=List.lengthstate.tests_runningletget_workersstate=List.rev_appendstate.idle_workers(List.rev_map(fun(_,{worker=worker;_})->worker)state.tests_running)letget_idle_workersstate=state.idle_workersletis_idle_workerworkerstate=List.exists(funworker'->worker==worker')state.idle_workersletget_tests_runningstate=List.mapfststate.tests_runningletrecnext_test_caseconfloggerstate=matchstate.tests_planned,state.idle_workerswith|[],_->Finished,state|_,worker::tl_workers->beginletchoice=state.chooser{OUnitChooser.tests_planned=List.mapfststate.tests_planned;tests_running=List.mapfststate.tests_running;tests_passed=List.mapfststate.tests_passed;cache=state.cache;}inmatchchoicewith|Choosetest_path->begintrylettest_length,test_fun=List.assoctest_pathstate.tests_plannedinletnow=OUnitUtils.now()inNext_test_case(test_path,test_fun,worker),{statewithtests_running=(test_path,{test_length=test_length;deadline=now+.delay_of_lengthtest_length;next_health_check=now+.state.health_check_interval;worker=worker;})::state.tests_running;tests_planned=filter_outtest_pathstate.tests_planned;idle_workers=tl_workers}withNot_found->assertfalseend|ChooseToPostpone->Try_again,state|ChooseToSkippath->letskipped_result=RSkip"Skipped by the chooser."inOUnitLogger.reportlogger(TestEvent(path,EStart));OUnitLogger.reportlogger(TestEvent(path,EResultskipped_result));OUnitLogger.reportlogger(TestEvent(path,EEnd));next_test_caseconflogger(add_test_resultsconf((path,skipped_result,None),[])state)|NoChoice->Finished,stateend|_,[]->Not_enough_worker,state(** Get all the results. *)letget_resultsstate=List.fold_right(fun(result,other_results)res->result::other_results@res)state.tests_passed[](** Get all the workers that need to be checked for their health. *)letget_worker_need_health_checkstate=letnow=OUnitUtils.now()inletrunning_workers=List.fold_left(funlst(test_path,test_running)->iftest_running.next_health_check<=nowthen(Sometest_path,test_running.worker)::lstelselst)[]state.tests_runninginletidle_workers=List.map(funworker->(None,worker))state.idle_workersinrunning_workers@idle_workers(** Update the activity of a worker, this postpone the next health check. *)letupdate_test_activitytest_pathstate=letnow=OUnitUtils.now()inlettests_running=List.fold_right(fun(test_path',test_running)lst->lettest_running=iftest_path'=test_paththen{test_runningwithnext_health_check=now+.state.health_check_interval}elsetest_runningin(test_path',test_running)::lst)state.tests_running[]in{statewithtests_running=tests_running}(** Get all the workers that are timed out, i.e. that need to be stopped. *)letget_worker_timed_outstate=letnow=OUnitUtils.now()inList.fold_left(funlst(test_path,test_running)->iftest_running.deadline<=nowthen(test_path,test_running.test_length,test_running.worker)::lstelselst)[]state.tests_running(** Compute when is the next time, we should either run health check or timeout
a test.
*)lettimeoutstate=letnow=OUnitUtils.now()inletnext_event_time=List.fold_left(funnext_event_time(_,test_running)->mintest_running.next_health_check(mintest_running.deadlinenext_event_time))(now+.state.health_check_interval)state.tests_runninginmax0.1(next_event_time-.now)