123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618moduleTest_result=structtypet=Success|Failure|Errorletto_exit_code=function|Success->0|Failure->2|Error->1;;letto_string=function|Success->"success"|Failure->"failure"|Error->"error";;letcombinet1t2=matcht1,t2with|Success,Success->Success|Error,_|_,Error->Error|Failure,_|_,Failure->Failure;;letcombine_allts=List.fold_leftcombineSuccesstsendletparse_argvargvlfmsg=tryArg.parse_argvargvlfmsgwith|Arg.Badmsg->Printf.eprintf"%s"msg;exit1|Arg.Helpmsg->Printf.printf"%s"msg;exit0;;typedescr=stringlettest_modules_ran=ref0lettest_modules_failed=ref0lettests_ran=ref0lettests_failed=ref0letdynamic_lib:stringoptionref=refNonetypefilename=stringtypeline_number=inttypestart_pos=inttypeend_pos=inttypeconfig=(moduleInline_test_config.S)type'atest_function_args=config:config->descr:descr->tags:stringlist->filename:filename->line_number:line_number->start_pos:start_pos->end_pos:end_pos->'amoduleTag_predicate=structtypet={required_tags:stringlist;dropped_tags:stringlist}letenable_everything={required_tags=[];dropped_tags=[]}letdropttag={dropped_tags=tag::t.dropped_tags;required_tags=List.filter((<>)tag)t.required_tags}letrequirettag={dropped_tags=List.filter((<>)tag)t.dropped_tags;required_tags=tag::t.required_tags}letentire_module_disabledt~partial_tags:tags=List.exists(fundropped->List.memdroppedtags)t.dropped_tagsletdisabledt~complete_tags:tags=List.exists(funreq->not(List.memreqtags))t.required_tags||List.exists(fundropped->List.memdroppedtags)t.dropped_tagsendtypewhich_tests={libname:string;only_test_location:(filename*line_numberoption*boolref)list;which_tags:Tag_predicate.t}typetest_mode={which_tests:which_tests;what_to_do:[`Run_partitionofstringoption|`List_partitions]}moduleAction:sigtypet=[|`Ignore|`Test_modeoftest_mode]valget:unit->tvalset:t->unitend=structtypet=[|`Ignore|`Test_modeoftest_mode]letaction:tref=ref`Ignoreletforce_drop=tryignore(Sys.getenv"FORCE_DROP_INLINE_TEST":string);truewithNot_found->falseletget()=(* This is useful when compiling to javascript.
Js_of_ocaml can statically evaluate [Sys.getenv "FORCE_DROP_INLINE_TEST"]
and inline the result ([`Ignore]) whenever [get ()] is called.
Unit tests can then be treated as deadcode since the argument [f] of the [test]
function below is never used. *)ifforce_dropthen`Ignoreelse!actionletsetv=action:=vendmodulePartition:sigvalfound_test:unit->unitvalset_current:string->unitvalis_current:stringoption->boolvalall:unit->stringlistend=structletall=Hashtbl.create23letcurrent=ref""letset_currentx=current:=xletfound_test()=if!current<>""&¬(Hashtbl.memall!current)thenHashtbl.addall!current();;letis_current=function|None->true|Somep->p=!current;;letall()=List.sortString.compare(Hashtbl.fold(funk()acc->k::acc)all[]);;endmoduleModule_context=structmoduleT=structtypeone_module={descr:string;tags:stringlist}typet=one_modulelistletdescrt=List.map(funm->m.descr)tlettagst=List.concat(List.map(funm->m.tags)t)endletcurrent:T.tref=ref[]letwith_~descr~tagsf=letprev=!currentincurrent:={T.descr;tags}::prev;tryf();current:=prev;withe->current:=prev;raiseeletcurrent_descr()=T.descr!currentletcurrent_tags()=T.tags!currentendletverbose=reffalseletstrict=reffalseletshow_counts=reffalseletlist_test_names=reffalseletdelayed_errors=ref[]letstop_on_error=reffalseletlog=refNonelettime_sec=ref0.letuse_color=reftrueletin_place=reffalseletdiff_command=refNoneletsource_tree_root=refNoneletallow_output_patterns=reffalseletdisplayed_descrdescrfilenamelinestart_posend_pos=Printf.sprintf"File %S, line %d, characters %d-%d%s"filenamelinestart_posend_posdescrletparse_descrstr=trySome(Scanf.sscanfstr" File %S , line %d , characters %d - %d %!"(funfileline_start_pos_end_pos->file,Someline))with_->trySome(Scanf.sscanfstr" File %S , line %d %!"(funfileline->file,Someline))with_->trySome(Scanf.sscanfstr" File %S %!"(funfile->file,None))with_->Noneletindent~by=function|""->""|str->letlen=String.lengthstrinletbuf=Buffer.create(len*2)inletindentation=String.makeby' 'inBuffer.add_stringbufindentation;fori=0tolen-1;doBuffer.add_charbufstr.[i];ifstr.[i]='\n'&&i<>len-1thenBuffer.add_stringbufindentationdone;Buffer.contentsbufletbacktrace_indented~by=letstr=Printexc.get_backtrace()inindentstr~bylet()=matchArray.to_listSys.argvwith|name::"inline-test-runner"::lib::restwhenBase.Exported_for_specific_uses.am_testing->begin(* when we see this argument, we switch to test mode *)lettests=ref[]inletlist_partitions=reffalseinletpartition=refNoneinlettag_predicate=refTag_predicate.enable_everythinginparse_argv(Array.of_list(name::rest))(Arg.align["-list-test-names",Arg.Unit(fun()->list_test_names:=true;verbose:=true)," Do not run tests but show what would have been run";"-list-partitions",Arg.Unit(fun()->list_partitions:=true)," Lists all the partitions that contain at least one test or test_module";"-partition",Arg.String(funi->partition:=Somei)," Only run the tests in the given partition";"-verbose",Arg.Setverbose," Show the tests as they run";"-stop-on-error",Arg.Setstop_on_error," Run tests only up to the first error \
(doesn't work for expect tests)";"-strict",Arg.Setstrict," End with an error if no tests were run";"-show-counts",Arg.Setshow_counts," Show the number of tests ran";"-log",Arg.Unit(fun()->(trySys.remove"inline_tests.log"with_->());log:=Some(open_out"inline_tests.log"))," Log the tests run in inline_tests.log";"-drop-tag",Arg.String(funs->tag_predicate:=Tag_predicate.drop!tag_predicates),"tag Only run tests not tagged with [tag] (overrides previous -require-tag)";"-require-tag",Arg.String(funs->tag_predicate:=Tag_predicate.require!tag_predicates),"tag Only run tests tagged with [tag] (overrides previous -drop-tag)";"-only-test",Arg.String(funs->letfilename,index=matchparse_descrswith|Some(file,index)->file,index|None->ifString.containss':'thenleti=String.indexs':'inletfilename=String.subs0iinletindex_string=String.subs(i+1)(String.lengths-i-1)inletindex=tryint_of_stringindex_stringwithFailure_->Printf.eprintf"Argument %s doesn't fit the format filename[:line_number]\n%!"s;exit1infilename,Someindexelses,Noneintests:=(filename,index,reffalse)::!tests),"location Run only the tests specified by all the -only-test options.
Locations can be one of these forms:
- file.ml
- file.ml:line_number
- File \"file.ml\"
- File \"file.ml\", line 23
- File \"file.ml\", line 23, characters 2-3";"-no-color",Arg.Clearuse_color," Summarize tests without using color";"-in-place",Arg.Setin_place," Update expect tests in place";"-diff-cmd",Arg.String(funs->diff_command:=Somes)," Diff command for tests that require diffing (use - to disable diffing)";"-allow-output-patterns",Arg.Setallow_output_patterns," Allow output patterns in tests expectations";"-source-tree-root",Arg.String(funs->source_tree_root:=Somes)," Path to the root of the source tree"])(funanon->Printf.eprintf"%s: unexpected anonymous argument %s\n%!"nameanon;exit1)(Printf.sprintf"%s %s %s [args]"name"inline-test-runner"lib);Action.set(`Test_mode{which_tests={libname=lib;only_test_location=!tests;which_tags=!tag_predicate;};what_to_do=if!list_partitionsthen`List_partitionselse`Run_partition!partition})end|_->()letam_test_runner=matchAction.get()with|`Test_mode_->true|`Ignore->falseletam_running_inline_test_env_var=(* for approximate compatibility, given that the variable is not exactly equivalent
to what PPX_INLINE_TEST_LIB_AM_RUNNING_INLINE_TEST used to be *)"TESTING_FRAMEWORK"(* This value is deprecated in principle, in favor of Core_kernel.am_running_test, so
we're going to live with the ugly pattern match. *)letam_running_inline_test=matchSys.getenv"PPX_INLINE_TEST_LIB_AM_RUNNING_INLINE_TEST"with|(_:string)->true(* for compatibility with people setting this variable directly *)|exceptionNot_found->matchSys.getenvam_running_inline_test_env_varwith|"inline-test"->true|exceptionNot_found->false|_->falselettesting=ifam_test_runnerthen`Testing`Am_test_runnerelse(ifam_running_inline_testthen`Testing`Am_child_of_test_runnerelse`Not_testing)letwall_time_clock_ns()=Time_now.nanoseconds_since_unix_epoch()lettime_without_resetting_random_seedsf=letbefore_ns=wall_time_clock_ns()inBase.Exn.protect~finally:(fun[@inline]()->time_sec:=Base.Int63.(wall_time_clock_ns()-before_ns|>to_float)/.1e9)~fletsaved_caml_random_state=lazy(Caml.Random.State.make[|100;200;300|])letsaved_base_random_state=lazy(Base.Random.State.make[|111;222;333|])lettime_and_reset_random_seedsf=letcaml_random_state=Caml.Random.get_state()inletbase_random_state=Base.Random.State.copyBase.Random.State.defaultinCaml.Random.set_state(Lazy.forcesaved_caml_random_state);Base.Random.set_state(Lazy.forcesaved_base_random_state);letresult=time_without_resetting_random_seedsfinCaml.Random.set_statecaml_random_state;Base.Random.set_statebase_random_state;resultletstring_of_module_descr()=String.concat""(List.map(funs->" in TES"^"T_MODULE at "^String.uncapitalize_asciis^"\n")(Module_context.current_descr()))letposition_matchdef_filenamedef_line_numberl=List.exists(fun(filename,line_number_opt,used)->letposition_start=String.lengthdef_filename-String.lengthfilenameinletfound=position_start>=0&&letend_of_def_filename=String.subdef_filenameposition_start(String.lengthfilename)inend_of_def_filename=filename&&(position_start=0||def_filename.[position_start-1]='/')&&(matchline_number_optwith|None->true|Someline_number->def_line_number=line_number)iniffoundthenused:=true;found)lletprint_delayed_errors()=matchList.rev!delayed_errorswith|[]->()|_::_asdelayed_errors->Printf.eprintf"\n%s\n%!"(String.make70'=');List.iter(funmessage->Printf.eprintf"%s%!"message)delayed_errorsleteprintf_or_delayfmt=Printf.ksprintf(funs->if!verbosethendelayed_errors:=s::!delayed_errorselsePrintf.eprintf"%s%!"s;if!stop_on_errorthenbeginprint_delayed_errors();exit2end)fmtletadd_hooks((moduleC):config)f=fun()->C.pre_test_hook();f()let[@inlinenever]test~config~descr~tags~filename:def_filename~line_number:def_line_number~start_pos~end_posf=matchAction.get()with|`Ignore->()|`Test_mode{which_tests={libname;only_test_location;which_tags};what_to_do}->letf=add_hooksconfigfinletdescr()=displayed_descrdescrdef_filenamedef_line_numberstart_posend_posinletcomplete_tags=tags@Module_context.current_tags()inletshould_run=Somelibname=!dynamic_lib&&beginmatchonly_test_locationwith|[]->true|_::_->position_matchdef_filenamedef_line_numberonly_test_locationend&¬(Tag_predicate.disabledwhich_tags~complete_tags)inifshould_runthenbeginmatchwhat_to_dowith|`List_partitions->Partition.found_test()|`Run_partitionpartition->ifPartition.is_currentpartitionthenbeginletdescr=descr()inincrtests_ran;beginmatch!logwith|None->()|Somech->Printf.fprintfch"%s\n%s"descr(string_of_module_descr())end;if!verbosethenbeginPrintf.printf"%s%!"descrend;letprint_time_taken()=(* If !list_test_names, this is is a harmless zero. *)if!verbosethenPrintf.printf" (%.3f sec)\n%!"!time_sec;intryletfailed=not!list_test_names&¬(time_and_reset_random_seedsf)inprint_time_taken();iffailedthenbeginincrtests_failed;eprintf_or_delay"%s is false.\n%s\n%!"descr(string_of_module_descr())endwithexn->print_time_taken();letbacktrace=backtrace_indented~by:2inincrtests_failed;letexn_str=Printexc.to_stringexninletsep=ifString.containsexn_str'\n'then"\n"else" "ineprintf_or_delay"%s threw%s%s.\n%s%s\n%!"descrsepexn_strbacktrace(string_of_module_descr())endendletset_lib_and_partitionstatic_libpartition=match!dynamic_libwith|Some_->(* possible if the interface is used explicitly or if we happen to dynlink something
that contain tests *)()|None->dynamic_lib:=Somestatic_lib;matchAction.get()with|`Ignore->()|`Test_mode{which_tests;what_to_do}->ifwhich_tests.libname=static_libthenbeginletrequires_partition=matchwhat_to_dowith|`List_partitions|`Run_partition(Some_)->true|`Run_partitionNone->falseinifpartition=""&&requires_partitionthenfailwith"ppx_inline_test: cannot use -list-partition or -partition \
without specifying a partition at preprocessing time"elsePartition.set_currentpartitionendletunset_libstatic_lib=match!dynamic_libwith|None->(* not giving an error, because when some annoying people put pa_ounit in their list
of preprocessors, pa_ounit is set up twice and we have two calls to unset_lib at
the end of the file, and the second one comes in this branch *)()|Somelib->iflib=static_libthendynamic_lib:=Nonelettest_unit~config~descr~tags~filename~line_number~start_pos~end_posf=test~config~descr~tags~filename~line_number~start_pos~end_pos(fun()->f();true)let[@inlinenever]test_module~config~descr~tags~filename:def_filename~line_number:def_line_number~start_pos~end_posf=matchAction.get()with|`Ignore->()|`Test_mode{which_tests={libname;only_test_location=_;which_tags};what_to_do}->letf=add_hooksconfigfinletdescr()=displayed_descrdescrdef_filenamedef_line_numberstart_posend_posinletpartial_tags=tags@Module_context.current_tags()inletshould_run=Somelibname=!dynamic_lib(* If, no matter what tags a test defines, we certainly will drop all tests within
this module, then don't run the module at all. This means people can write
things like the following without breaking the 32-bit build:
let%test_module [@tags "64-bits-only"] = (module struct
let i = Int64.to_int_exn ....
end)
We don't shortcut based on position, as we can't tell what positions the
inner tests will have. *)&¬(Tag_predicate.entire_module_disabledwhich_tags~partial_tags)inifshould_runthenbeginmatchwhat_to_dowith|`List_partitions->Partition.found_test()|`Run_partitionpartition->ifPartition.is_currentpartitionthenbeginincrtest_modules_ran;letdescr=descr()intryModule_context.with_~descr~tags(fun()->(* We do not reset random states upon entering [let%test_module].
Con: Code in test modules can accidentally depend on top-level random
state effects.
Pros: (1) We don't reset to the same seed on entering a [let%test_module]
and then a [let%test] inside that module, which could lead to
accidentally randomly generating the same values in some test. (2) Moving
code into and out of [let%test_module] does not change its random seed.
*)time_without_resetting_random_seedsf)withexn->letbacktrace=backtrace_indented~by:2inincrtest_modules_failed;letexn_str=Printexc.to_stringexninletsep=ifString.containsexn_str'\n'then"\n"else" "ineprintf_or_delay("TES"^^"T_MODULE at %s threw%s%s.\n%s%s\n%!")(String.uncapitalize_asciidescr)sepexn_strbacktrace(string_of_module_descr())endendletsummarize()=matchAction.get()with|`Ignore->ifSys.argv<>[||]&&Filename.basenameSys.argv.(0)="inline_tests_runner.exe"thenPrintf.eprintf"inline_tests_runner.exe is not supposed to be run by hand, you \n\
should run the inline_tests_runner script instead.\n%!"elsePrintf.eprintf"You are doing something unexpected with the tests. No tests have \n\
been run. You should use the inline_tests_runner script to run \n\
tests.\n%!";Test_result.Error|`Test_mode{which_tests=_;what_to_do=`List_partitions}->List.iter(Printf.printf"%s\n")(Partition.all());Test_result.Success|`Test_mode{what_to_do=`Run_partition_;which_tests}->beginbeginmatch!logwith|None->()|Somech->close_outchend;print_delayed_errors();match!tests_failed,!test_modules_failedwith|0,0->beginif!show_countsthenbeginPrintf.eprintf"%d tests ran, %d test_modules ran\n%!"!tests_ran!test_modules_ranend;leterrors=letunused_tests=List.filter(fun(_,_,used)->not!used)which_tests.only_test_locationinmatchunused_testswith|[]->None|_::_->Someunused_testsinmatcherrorswith|Sometests->Printf.eprintf"ppx_inline_test error: the following -only-test flags matched nothing:";List.iter(fun(filename,line_number_opt,_)->matchline_number_optwith|None->Printf.eprintf" %s"filename|Someline_number->Printf.eprintf" %s:%d"filenameline_number)tests;Printf.eprintf".\n%!";Test_result.Error|None->if!tests_ran=0&&!strictthenbeginPrintf.eprintf"ppx_inline_test error: no tests have been run.\n%!";Test_result.ErrorendelsebeginTest_result.Successendend|count,count_test_modules->Printf.eprintf"FAILED %d / %d tests%s\n%!"count!tests_ran(ifcount_test_modules=0then""elsePrintf.sprintf(", %d TES"^^"T_MODULES")count_test_modules);Test_result.Failureendletuse_color=!use_colorletin_place=!in_placeletdiff_command=!diff_commandletsource_tree_root=!source_tree_rootletallow_output_patterns=!allow_output_patternsletevaluators=ref[summarize]letadd_evaluator~f=evaluators:=f::!evaluatorsletexit()=List.map(funf->f())(List.rev!evaluators)|>Test_result.combine_all|>Test_result.to_exit_code|>exit