123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266moduleMisc=struct(* As usual, OCaml stdlib is poor and needed to extend *)external(&):('a->'b)->'a->'b="%apply"(** (@@) but one less char *)let(!!%)=Format.eprintflet(+::=)rx=r:=x::!rendopenMisc(** test type *)modulePrimitives=struct(* OUnit style test primitives *)typet=|Testof(unit->bool)|LabeledofLongident.t*t|Listoftlist|LocationofLocation.t*tletfun_f=Testfletidentlidt=Labeled(lid,t)letlabelst=Labeled(Lidents,t)letlistts=ListtsendincludePrimitivesmoduleName=struct(* { label= None; location= None } is easy to be duped, but we do not care *)typet={label:Longident.toption;location:Location.toption;}letnull={label=None;location=None}letadd_labellt=matcht.labelwith|None->{twithlabel=Somel}|Somepl->{twithlabel=Some(Longident.concatpll)}letset_locationlst={stwithlocation=Somel}letto_stringn=matchn.labelwith|None->"_"|Somel->Longident.to_stringlletlocationn=matchn.locationwith|None->assertfalse|Somel->lendmoduleConfig=structtypearg=|DoofRe.re|DontofRe.retypet={args:arglist;show_tests:bool;verbose:bool;default_go:bool;print_failure_immediately:bool;stop_at_nth:int;}letrev_args=ref[]letshow_tests=reffalselettest_verbose=reffalseletprint_failure_immediately=reftrueletstop_at_nth=ref0(* never *)letfrom_argsdefault_go={args=List.rev!rev_args;show_tests=!show_tests;verbose=!test_verbose;default_go=default_go;print_failure_immediately=!print_failure_immediately;stop_at_nth=!stop_at_nth}letis_goconfn=lets=Name.to_stringninList.fold_left(funst->function|DorexwhenRe.Pcre.pmatch~rexs->true|DontrexwhenRe.Pcre.pmatch~rexs->false|_->st)conf.default_goconf.argsendopenConfigletarg_specs=letopenArginletopenConfigin["--test-go",String(funs->rev_args+::=Do(Re.Pcre.regexps)),"<string>: Perform tests match with the string";"--test-skip",String(funs->rev_args+::=Dont(Re.Pcre.regexps)),"<string>: Skip tests match with the string";"--test-verbose",Settest_verbose,": Print out what is being tested.";"--test-show",Setshow_tests,": List all the tests. Do not perform any test.";"--test-print-immediately",Setprint_failure_immediately,": Show test errors immediately";"--test-max-failures",Int(funi->stop_at_nth:=i),": Stop at n-th test failure. 0 means never.";]moduleError=structtypet=[`Exnofexn*Printexc.raw_backtrace|`False]openFormatletformatfmt=function|`Exn(exn,bt)->fprintffmt"@[<v>Exn: %s@ Backtrace: %s@]"(Printexc.to_stringexn)(Printexc.raw_backtrace_to_stringbt)|`False->fprintffmt"false"endmoduleResult=structtypet={time:float;(* in sec *)result:[`Okofunit|`ErrorofError.t]}endletrecfoldstnf=function|Testt->fstnt|Labeled(l,t)->letn=Name.add_labellninfoldstnft|Location(loc,t)->letn=Name.set_locationlocninfoldstnft|Listts->List.fold_left(funstt->foldstnft)sttsletshowconf()nt=letf()n_=!!%"%a: %s: %s@."Location.format(Name.locationn)(Name.to_stringn)(ifConfig.is_goconfnthen"go"else"skip")infold()nftletreport_one(n,{Result.time=_;result})=letopenFormatinletname=Name.to_stringninmatchresultwith|`Ok()->!!%"Test succeeded: %s at %a@."name(funppf->function|None->fprintfppf"<no location>"|Somel->Location.formatppfl)n.Name.location;|`Errore->!!%"Test failed: %s at %a@."name(funppf->function|None->fprintfppf"<no location>"|Somel->Location.formatppfl)n.Name.location;!!%"%a@.------@."Error.formatemoduleReport=structtypet=(Name.t*Result.t)list*int(** num of errors *)letprint(reslist,errors)=!!%"Test finished@.";letnum_tests=List.lengthreslistin!!%" Performed: %d@."num_tests;!!%" Succeeded: %d@."(num_tests-errors);!!%" Failure: %d@."errors;!!%"---@.";List.iter(function|(_n,{Result.result=`Ok()})->()|x->report_onex)reslistletprint_then_exit(_,errorsasres)=printres;exit(iferrors<>0then-1else0)endletabortconf(_reslist,errorsasst)=!!%"Too many test failures %d >= %d@."errorsconf.stop_at_nth;Report.printst;!!%"Too many test failures %d >= %d. Aborted.@."errorsconf.stop_at_nth;exit(-1)letrunconfstnt=letf(reslist,errorsasst)nt=ifConfig.is_goconfnthenbeginletname=Name.to_stringninifconf.verbosethen!!%"Test %s...@."name;letres=tryift()then`Ok()else`Error`Falsewith|e->`Error(`Exn(e,Printexc.get_raw_backtrace()))inletis_error=matchreswith`Ok_->false|_->trueinifconf.verbosethen!!%"Test %s %s@."name(ifis_errorthen"failed"else"done");letx=(n,{Result.time=0.0;result=res})inifis_error&&conf.print_failure_immediatelythenreport_onex;leterrors=matchreswith`Ok_->errors|_->errors+1inletst=(x::reslist,errors)inifconf.stop_at_nth>0&&errors>=conf.stop_at_nththenbeginabortconfstendelsestendelsestinfoldstnft(** Global test table *)letrev_tests=ref[]letaddt=rev_tests:=t::!rev_testsletlocationloct=Location(loc,t)(* TEST_UNIT *)lettest_unitloclf=rev_tests:=(locationloc&identl&fun_(fun()->f();true))::!rev_tests(* TEST *)lettestloclf=rev_tests:=(locationloc&identl&fun_f)::!rev_tests(* TEST_FAIL *)lettest_failloclf=rev_tests:=(locationloc&identl&fun_(fun()->tryf();falsewith_->true))::!rev_testsletfold_testsfinit=letrecloopst=function|[]->st|t::ts->letst=fstName.nulltinloopsttsinloopinit&List.rev!rev_tests(* Running tests can add more tests, therefore we must loop things.
Not sophisticated :-( *)letrun_testsdefault=letconf=Config.from_argsdefaultinifconf.show_teststhenbeginfold_tests(showconf)();exit0endelseletress,errors=fold_tests(runconf)([],0)inList.revress,errorsletcollect()=Arg.parsearg_specs(funs->!!%"test command does not take anonymous arguments: %s@."s;exit2)"test";run_teststrue|>Report.print_then_exit(** {2 Library for test code} *)moduleTestTool=structletmust_raisef=tryignore&f();falsewith_->trueend