123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306(* This file is part of Learn-OCaml.
*
* Copyright (C) 2022-2023 OCaml Software Foundation.
*
* Learn-OCaml is distributed under the terms of the MIT license. See the
* included LICENSE file for details. *)openTest_lib.Open_meopenLearnocaml_reporttype'atest_result=|Pass|Failof'a|Errofexntype'amutant_info=string*int*'aletuncurry2f=fun(x,y)->fxyletuncurry3f=fun(x,y,z)->fxyzletuncurry4f=fun(x,y,z,w)->fxyzwletmap_thirdf=fun(x,y,z)->(x,y,fz)(* module Make (Test_lib: module type of Test_lib) : S = struct *)moduleM=structopenTest_libletrun_test_against?(compare=(=))f(input,expected)=tryletrun_f()=finputinletoutput=run_timeoutrun_finifcompareoutputexpectedthenPasselseFailoutputwithexn->Errexnletrun_test_against_mutant?(compare=(=))f(input,expected)=matchrun_test_against~comparef(input,expected)with|Pass->false|_->truelettyped_printertyppfv=Introspection.print_valueppfvtyletprint_withty=Format.asprintf"%a"(typed_printerty)letstring_of_exn=print_with[%ty:exn]lettest_against_mutant~compare(name,points,mut)numtests=letresult=List.exists(run_test_against_mutant~comparemut)testsinifresultthenMessage([Text"Your tests successfully revealed the bug in implementation";Textnum;Text": ";Textname],Successpoints)elseMessage([Text"Your tests did not expose the bug in implementation";Textnum],Failure)lettest_against_fn~compare?(show_output=false)fprinterout_printer(input,expected)=letmsg=Message([Text"Running test";Code(printerinput)],Informative)inletexpected_str=out_printerexpectedinletresult=run_test_against~comparef(input,expected)inletreport=matchresultwith|Pass->[Message([Text"Test passed with output";Codeexpected_str],Important)]|Failout->[Message([Text"Test failed: expected output";Codeexpected_str;Text"but got";ifshow_outputthenCode(out_printerout)elseText"something else"],Failure)]|Errexn->[Message([Text"Test failed: expected output";Codeexpected_str;Text"but got an unexpected exception";Code(string_of_exnexn)],Failure)]inmsg::reportletsection_header="Your tests..."letsoln_header="...against the solution"letmutation_header="...against our buggy implementations"letstud_header="...against your implementation"lettest_against_mutants~comparemutstests=letstring_of_numx="#"^(string_of_intx)inlettest_against_mutant_iimut=test_against_mutant~comparemut(string_of_num(succi))testsinList.mapitest_against_mutant_imutslettest_reportsoln_reportstud_sectionmaybe_mut_report=letsoln_section=Section([Textsoln_header],soln_report)inletmut_report=matchmaybe_mut_reportwith|None->Message([Text"Some of your tests are incorrect and need to be fixed"],Failure)|Somereport->Section([Textmutation_header],report)insoln_section::mut_report::stud_sectionletpassed_mutation_testingreport=matchreportwith|[Section([Texttitle],items)]whenString.equaltitlesection_header->(* Remove the student implementation section, if present *)letreport'=List.filter(function|Section([Texttitle],_)->not(String.equaltitlestud_header)|_->true)itemsinnot(snd(Learnocaml_report.resultreport'))|_->falsetype'alookup=|UnboundofLearnocaml_report.t|Foundof'aletno_test_cases_report=[Message([Text"You have not yet written any test cases."],Failure)]letsoln_not_found_msg=Message([Text"Reference solution not found.";Text"This is an error with the grader.";Text"Please contact your instructor."],Failure)letappend_mapfl=List.fold_right(funxacc->(fx)@acc)l[]lettest_soln_report~comparesolnprinterout_printertests=matchsolnwith|Unboundreport->soln_not_found_msg::report|Foundsoln->lettester=test_against_fn~comparesolnprinterout_printerinappend_maptestertestslettest_stud_section~comparestudprinterout_printertests=matchstudwith|None->[]|Somelookup->letstud_report=matchlookupwith|Unboundreport->report|Foundstud->lettester=test_against_fn~compare~show_output:truestudprinterout_printerinappend_maptestertestsin[Section([Textstud_header],stud_report)]lettest~comparetest_typrinterout_printernamesolnstudmuts=lettest_name=name^"_tests"inletreport=test_variable_propertytest_tytest_name@@funtests->ifList.lengthtests=0thenno_test_cases_reportelseletsoln_report=test_soln_report~comparesolnprinterout_printertestsinletstud_section=test_stud_section~comparestudprinterout_printertestsinletmaybe_mut_report=ifsnd(Learnocaml_report.resultsoln_report)thenNoneelseSome(test_against_mutants~comparemutstests)intest_reportsoln_reportstud_sectionmaybe_mut_reportin[Section([Textsection_header],report)]letprocess_lookupprocesslookuptyname=matchlookuptyname()with|`Unbound(_,report)->Unboundreport|`Found(_,_,data)->Found(processdata)lettest_unit_tests_1?(test_student_soln=true)?test:(compare=(=))tynamemuts=let(domain,range)=Ty.domainstyinlettest_ty=Ty.lst(Ty.pair2domainrange)inletin_printer=typed_printerdomaininletprinterinput=Format.asprintf"@[<hv 2>%s@ %a@]"namein_printerinputinletout_printer=print_withrangeinletsoln=process_lookup(funx->x)lookup_solutiontynameinletstud=iftest_student_solnthenSome(process_lookup(funx->x)lookup_studenttyname)elseNoneintest~comparetest_typrinterout_printernamesolnstudmutslettest_unit_tests_2?(test_student_soln=true)?test:(compare=(=))tynamemuts=let(dom1,rng)=Ty.domainstyinlet(dom2,range)=Ty.domainsrnginlettest_ty=Ty.lst(Ty.pair2(Ty.pair2dom1dom2)range)inletin1_printer=typed_printerdom1inletin2_printer=typed_printerdom2inletprinter(in1,in2)=Format.asprintf"@[<hv 2>%s@ %a@ %a@]"namein1_printerin1in2_printerin2inletout_printer=print_withrangeinletmuts=List.map(map_thirduncurry2)mutsinletsoln=process_lookupuncurry2lookup_solutiontynameinletstud=iftest_student_solnthenSome(process_lookupuncurry2lookup_studenttyname)elseNoneintest~comparetest_typrinterout_printernamesolnstudmutslettest_unit_tests_3?(test_student_soln=true)?test:(compare=(=))tynamemuts=let(dom1,rng1)=Ty.domainstyinlet(dom2,rng2)=Ty.domainsrng1inlet(dom3,range)=Ty.domainsrng2inlettest_ty=Ty.lst(Ty.pair2(Ty.pair3dom1dom2dom3)range)inletin1_printer=typed_printerdom1inletin2_printer=typed_printerdom2inletin3_printer=typed_printerdom3inletprinter(in1,in2,in3)=Format.asprintf"@[<hv 2>%s@ %a@ %a@ %a@]"namein1_printerin1in2_printerin2in3_printerin3inletout_printer=print_withrangeinletmuts=List.map(map_thirduncurry3)mutsinletsoln=process_lookupuncurry3lookup_solutiontynameinletstud=iftest_student_solnthenSome(process_lookupuncurry3lookup_studenttyname)elseNoneintest~comparetest_typrinterout_printernamesolnstudmutslettest_unit_tests_4?(test_student_soln=true)?test:(compare=(=))tynamemuts=let(dom1,rng1)=Ty.domainstyinlet(dom2,rng2)=Ty.domainsrng1inlet(dom3,rng3)=Ty.domainsrng2inlet(dom4,range)=Ty.domainsrng3inlettest_ty=Ty.lst(Ty.pair2(Ty.pair4dom1dom2dom3dom4)range)inletin1_printer=typed_printerdom1inletin2_printer=typed_printerdom2inletin3_printer=typed_printerdom3inletin4_printer=typed_printerdom4inletprinter(in1,in2,in3,in4)=Format.asprintf"@[<hv 2>%s@ %a@ %a@ %a@ %a@]"namein1_printerin1in2_printerin2in3_printerin3in4_printerin4inletout_printer=print_withrangeinletmuts=List.map(map_thirduncurry4)mutsinletsoln=process_lookupuncurry4lookup_solutiontynameinletstud=iftest_student_solnthenSome(process_lookupuncurry4lookup_studenttyname)elseNoneintest~comparetest_typrinterout_printernamesolnstudmutsendincludeM(* for backwards-compatibility *)moduleMake(_:moduletypeofTest_lib)=M