123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577(**************************************************************************)(* 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. *)(**************************************************************************)openOUnitTestopenOUnitLogger(** Number of shards to use. The way the shards are used depends on the type of
runner.
*)letshards=letshards=ref2inifSys.os_type="Unix"thenbeginifSys.file_exists"/proc/cpuinfo"thenbeginletchn_in=open_in"/proc/cpuinfo"inlet()=trywhiletruedotryletline=input_linechn_ininScanf.sscanfline"cpu cores : %d"(funi->shards:=maxi2)withScanf.Scan_failure_->()donewithEnd_of_file->()inclose_inchn_inendend;OUnitConf.make_int"shards"!shards"Number of shards to use as worker (threads or processes)."(** Whether or not run a Gc.full_major in between tests. This adds time
when running tests, but helps to avoid unexpected error due to finalisation
of values allocated during a test.
*)letrun_gc_full_major=OUnitConf.make_bool"run_gc_full_major"true"Run a Gc.full_major in between tests."(** Common utilities to run test. *)letrun_one_testconfloggersharedtest_path(test_fun:OUnitTest.test_fun)=let()=OUnitLogger.reportlogger(TestEvent(test_path,EStart))inletnon_fatal=ref[]inletmain_result_full=with_ctxtconfloggersharednon_fataltest_path(functxt->letcheck_env=OUnitCheckEnv.create()inletresult_full=trytest_functxt;OUnitCheckEnv.checkctxtcheck_env;ifrun_gc_full_majorconfthenbeginGc.full_major();end;test_path,RSuccess,Nonewithe->OUnitTest.result_full_of_exceptionctxteinreport_result_fullctxtresult_full)inletresult_full,other_result_fulls=matchmain_result_full,List.rev!non_fatalwith|(_,RSuccess,_),[]->main_result_full,[]|(_,RSuccess,_),hd::tl->OUnitResultSummary.worst_result_fullhdtl|_,lst->OUnitResultSummary.worst_result_fullmain_result_fulllstinOUnitLogger.reportlogger(TestEvent(test_path,EEnd));result_full,other_result_fullstyperunner=OUnitConf.conf->OUnitTest.logger->OUnitChooser.chooser->(path*test_length*test_fun)list->OUnitTest.result_list(* The simplest runner possible, run test one after the other in a single
* process, without threads.
*)(* Run all tests, sequential version *)letsequential_runner:runner=funconfloggerchoosertest_cases->letshared=OUnitShared.create()inletreciterstate=matchOUnitState.next_test_caseconfloggerstatewith|OUnitState.Finished,state->OUnitState.get_resultsstate|OUnitState.Next_test_case(test_path,test_fun,worker),state->iter(OUnitState.test_finishedconf(run_one_testconfloggersharedtest_pathtest_fun)workerstate)|(OUnitState.Try_again|OUnitState.Not_enough_worker),_->assertfalseinletstate=OUnitState.add_worker()(OUnitState.createconfchoosertest_cases)initerstate(**/**)(* Plugin interface. *)modulePlugin=OUnitPlugin.Make(structtypet=runnerletname="runner"letconf_help="Select a the method to run tests."letdefault_name="sequential"letdefault_value=sequential_runnerend)(**/**)includePlugin(** Build worker based runner. *)moduleGenericWorker=structopenOUnitStatetypemessage_to_worker=|Exit|AckLockofbool|RunTestofpathletstring_of_message_to_worker=function|Exit->"Exit"|AckLock_->"AckLock _"|RunTest_->"RunTest _"typemessage_from_worker=|AckExit|LogofOUnitTest.log_event_t|Lockofint|Unlockofint|TestDoneof(OUnitTest.result_full*OUnitTest.result_list)letstring_of_message_from_worker=function|AckExit->"AckExit"|Log_->"Log _"|Lock_->"Lock _"|Unlock_->"Unlock _"|TestDone_->"TestDone _"moduleMapPath=Map.Make(structtypet=pathletreccomparelst1lst2=matchlst1,lst2with|hd1::tl1,hd2::tl2->beginmatchStdlib.comparehd1hd2with|0->comparetl1tl2|n->nend|[],_::_->-1|_::_,[]->1|[],[]->0end)typemap_test_cases=(OUnitTest.path*OUnitTest.test_length*(OUnitTest.ctxt->unit))MapPath.ttype('a,'b)channel={send_data:'a->unit;receive_data:unit->'b;close:unit->unit;}typeworker_channel=(message_from_worker,message_to_worker)channel(* Add some extra feature to channel. *)letwrap_channelshard_idstring_of_read_messagestring_of_written_messagechannel=(* Turn on to debug communication in channel. *)letdebug_communication=falseinifdebug_communicationthenbeginletdebugffmt=Printf.ksprintf(funs->ifdebug_communicationthenprerr_endline("D("^shard_id^"): "^s))fmtinletsend_datamsg=debugf"Sending message %S"(string_of_written_messagemsg);channel.send_datamsg;debugf"Message transmitted, continuing."inletreceive_data()=let()=debugf"Waiting to receive data."inletmsg=channel.receive_data()indebugf"Received message %S"(string_of_read_messagemsg);msgin{send_data=send_data;receive_data=receive_data;close=channel.close;}endelsebeginchannelend(* Run a worker, react to message receive from parent. *)letmain_worker_loop~yield~shard_id~worker_log_file(conf:OUnitConf.conf)(channel:worker_channel)(map_test_cases:map_test_cases)=letlogger=letmaster_logger=set_shardshard_id(OUnitLogger.fun_logger(fun{event=log_ev;_}->channel.send_data(Loglog_ev))ignore)inletbase_logger=ifworker_log_filethenOUnitLoggerStd.create_file_loggerconfshard_idelseOUnitLogger.null_loggerinOUnitLogger.combine[base_logger;master_logger]inletshared=lettry_lockid=channel.send_data(Lockid);matchchannel.receive_data()with|AckLockb->b|Exit|RunTest_->assertfalseinletreclockid=ifnot(try_lockid)thenbeginyield();lockidendelsebegin()endinletunlockid=channel.send_data(Unlockid);inletglobal={OUnitShared.lock=lock;try_lock=try_lock;unlock=unlock;}in{OUnitShared.global=global;process=OUnitShared.noscope_create();}inletrecloop()=matchchannel.receive_data()with|Exit->channel.send_dataAckExit|RunTesttest_path->lettest_path,_,test_fun=MapPath.findtest_pathmap_test_casesinletres=run_one_testconfloggersharedtest_pathtest_funinchannel.send_data(TestDoneres);loop()|AckLock_->loop()inloop()type'aworker={channel:(message_to_worker,message_from_worker)channel;close_worker:unit->stringoption;select_fd:'a;shard_id:string;is_running:unit->bool;}type'aworker_creator=shard_id:string->master_id:string->worker_log_file:bool->OUnitConf.conf->map_test_cases->'aworkertype'aworkers_waiting_selector=timeout:float->'aworkerlist->'aworkerlist(* Run all tests. *)letrunner(create_worker:'aworker_creator)(workers_waiting:'aworkers_waiting_selector):runner=fun(conf:OUnitConf.conf)loggerchoosertest_cases->letmap_test_cases=List.fold_left(funmp((test_path,_,_)astest_case)->MapPath.addtest_pathtest_casemp)MapPath.emptytest_casesinletstate=OUnitState.createconfchoosertest_casesinletshards=max(shardsconf)1inletmaster_id=logger.OUnitLogger.lshardinletworker_idx=ref1inlettest_per_worker,incr_tests_per_worker=OUnitUtils.make_counter()inlethealth_check_per_worker,incr_health_check_per_worker=OUnitUtils.make_counter()inlet()=infoflogger"Using %d workers maximum."shards;inletworker_log_file=ifnot(OUnitLoggerStd.is_output_file_shard_dependentconf)thenbeginwarningflogger"-output-file doesn't include $(shard_id), \
shards won't have file log.";falseendelsebegintrueendinletmaster_shared=OUnitShared.noscope_create()in(* Act depending on the received message. *)letprocess_messageworkermsgstate=matchmsgwith|AckExit->letmsg_opt=infoflogger"Worker %s has ended."worker.shard_id;worker.close_worker()inOUnitUtils.opt(errorflogger"Worker return status: %s")msg_opt;remove_idle_workerworkerstate|Loglog_ev->OUnitLogger.report(set_shardworker.shard_idlogger)log_ev;state|Lockid->worker.channel.send_data(AckLock(master_shared.OUnitShared.try_lockid));state|Unlockid->master_shared.OUnitShared.unlockid;state|TestDonetest_result->OUnitState.test_finishedconftest_resultworkerstatein(* Report a worker dead and unregister it. *)letdeclare_dead_workertest_pathworkerresultstate=letlog_pos=positionloggerinreportlogger(TestEvent(test_path,EResultresult));reportlogger(TestEvent(test_path,EEnd));remove_idle_workerworker(test_finishedconf((test_path,result,log_pos),[])workerstate)inletdeclare_dead_idle_workerworkerstate=letmsg=Printf.sprintf"Worker %s died unexpectedly."worker.shard_idinreportlogger(GlobalEvent(GLog(`Info,msg)));remove_idle_workerworkerstatein(* Kill the worker that has timed out. *)letkill_timeoutstate=List.fold_left(funstate(test_path,test_length,worker)->let_msg:stringoption=errorflogger"Worker %s, running test %s has timed out."worker.shard_id(string_of_pathtest_path);worker.close_worker()indeclare_dead_workertest_pathworker(RTimeouttest_length)state)state(get_worker_timed_outstate)in(* Check that worker are healthy (i.e. still running). *)letcheck_healthstate=List.fold_left(funstate(test_path_opt,worker)->incr_health_check_per_workerworker.shard_id;ifworker.is_running()thenbeginmatchtest_path_optwith|Sometest_path->update_test_activitytest_pathstate|None->stateendelsebeginmatchtest_path_optwith|Sometest_path->begin(* Argh, a test failed badly! *)letresult_msg=errorflogger"Worker %s, running test %s is not running anymore."worker.shard_id(string_of_pathtest_path);matchworker.close_worker()with|Somemsg->Printf.sprintf"Worker stops running: %s"msg|None->"Worker stops running for unknown reason."indeclare_dead_workertest_pathworker(RError(result_msg,None))stateend|None->declare_dead_idle_workerworkerstateend)state(get_worker_need_health_checkstate)in(* Main wait loop. *)letwait_test_donestate=letstate=(check_health(kill_timeoutstate))inifget_workersstate<>[]thenbeginletworkers_waiting_lst=infoflogger"%d tests running: %s."(count_tests_runningstate)(String.concat", "(List.mapstring_of_path(get_tests_runningstate)));workers_waiting~timeout:(timeoutstate)(get_workersstate)inList.fold_left(funstateworker->process_messageworker(worker.channel.receive_data())state)stateworkers_waiting_lstendelsebeginstateendin(* Wait for every worker to stop. *)letrecwait_stoppedstate=ifOUnitState.get_workersstate=[]thenstateelsewait_stopped(wait_test_donestate)inletreciterstate=matchOUnitState.next_test_caseconfloggerstatewith|Not_enough_worker,state->ifOUnitState.count_workerstate<shardsthenbegin(* Start a worker. *)letshard_id=OUnitUtils.shardf!worker_idxinlet()=infoflogger"Starting worker number %s."shard_idinletworker=create_worker~shard_id~master_id~worker_log_fileconfmap_test_casesinlet()=infoflogger"Worker %s started."worker.shard_idinletstate=add_workerworkerstateinincrworker_idx;iterstateendelsebeginiter(wait_test_donestate)end|Try_again,state->iter(wait_test_donestate)|Next_test_case(test_path,_,worker),state->incr_tests_per_workerworker.shard_id;worker.channel.send_data(RunTesttest_path);iterstate|Finished,state->letcount_tests_running=OUnitState.count_tests_runningstateinifcount_tests_running=0thenbeginletstate=List.iter(funworker->worker.channel.send_dataExit)(OUnitState.get_workersstate);wait_stoppedstateininfoflogger"Used %d worker during test execution."(!worker_idx-1);List.iter(fun(shard_id,count)->infoflogger"Run %d tests with shard %s."countshard_id)(test_per_worker());List.iter(fun(shard_id,count)->infoflogger"Check health of shard %s, %d times."shard_idcount)(health_check_per_worker());OUnitState.get_resultsstateendelsebegininfoflogger"Still %d tests running : %s."count_tests_running(String.concat", "(List.mapstring_of_path(get_tests_runningstate)));iter(wait_test_donestate)endiniterstateend