123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2017-2019 The MOPSA Project. *)(* *)(* This program is free software: you can redistribute it and/or modify *)(* it under the terms of the GNU Lesser General Public License as published *)(* by the Free Software Foundation, either version 3 of the License, or *)(* (at your option) any later version. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* You should have received a copy of the GNU Lesser General Public License *)(* along with this program. If not, see <http://www.gnu.org/licenses/>. *)(* *)(****************************************************************************)(** Unit tests iterator. *)openMopsaopenSig.Abstraction.StatelessopenAstletname="universal.iterators.unittest"(* Command line options
====================
- The flag -unittest activates the mode of unit testing. In this
mode, every function starting with test_* is called. At the end,
statistics about failed/safe assertions are collected.
- The option -unittest-filter = f1,f2,... selects the functions
to be tested.
*)letunittest_flag=reffalseletunittest_filter=ref[]let()=register_domain_optionname{key="-unittest";category="Unit tests";spec=Setunittest_flag;doc=" activate unittest mode";default="";};register_domain_optionname{key="-unittest-filter";category="Unit tests";doc=" list of test functions (separated by comma) to analyze";spec=String((funs->unittest_filter:=Str.split(Str.regexp"[ ]*,[ ]*")s),ArgExt.empty);default="";};()(* Flow tokens
===========
Safe assertions are saved in the flow so that we can compute
statistics at the end of the analysis. Note that failed assertions
are kept in the T_alarm token.
*)typetoken+=|T_safe_assertofrange(* location of the assert statement *)let()=register_token{compare=(funnexttk1tk2->matchtk1,tk2with|T_safe_assert(r1),T_safe_assert(r2)->compare_ranger1r2|_->nexttk1tk2);print=(funnextfmttk->matchtkwith|T_safe_assert(r)->Format.fprintffmt"safe@%a"pp_ranger|_->nextfmttk);};()(* Analysis alarms *)typecheck+=CHK_ASSERT_FAILtypealarm_kind+=A_assert_failofexpr(** condition *)let()=register_check(funnextfmt->function|CHK_ASSERT_FAIL->Format.fprintffmt"Assertion failure"|a->nextfmta);register_alarm{check=(funnext->function|A_assert_fail_->CHK_ASSERT_FAIL|a->nexta);compare=(funnexta1a2->matcha1,a2with|A_assert_fail(c1),A_assert_fail(c2)->compare_exprc1c2|_->nexta1a2);print=(funnextfmt->function|A_assert_fail(cond)->Format.fprintffmt"Assertion '%a' violated"(Debug.boldpp_expr)cond|a->nextfmta);join=(funnexta1a2->nexta1a2)};()letraise_assert_fail?(force=false)condmanflow=letcs=Flow.get_callstackflowinletalarm=mk_alarm(A_assert_failcond)cscond.erangeinFlow.raise_alarmalarm~bottom:true~forceman.latticeflowletsafe_assert_checkrangemanflow=Flow.add_safe_checkCHK_ASSERT_FAILrangeflowmoduleDomain=struct(* Domain identification *)(* ===================== *)includeGenStatelessDomainId(structletname=nameend)letsummaryfmt=Debug.debug~channel:"unittest"fmtletchecks=[CHK_ASSERT_FAIL](* Initialization *)(* ============== *)letinitprogmanflow=None(* Computation of post-conditions *)(* ============================== *)letexecute_test_functions?(flow_cleaner=funmanflow->Flow.removeT_curflow)testsmanflow=lettests=match!unittest_filterwith|[]|["all"]->tests|_->List.filter(fun(t,_)->List.memt!unittest_filter)testsinletctx=Flow.get_ctxflowinletreport=Flow.get_reportflowinList.fold_left(funacc(name,test)->debug"Executing %s"name;(* Fold the context *)letflow=Flow.copy_ctxaccflowin(* Call the function *)letflow1=man.exectestflow|>post_to_flowmaninletflow1=flow_cleanermanflow1in(* let info_flow1 = Flow.bottom (Flow.get_ctx flow1) (Flow.get_report flow1) in *)Flow.joinman.latticeaccflow1)(Flow.bottomctxreport)testsletrecexecstmtmanflow=matchskindstmtwith|S_unit_tests(tests)->debug"Starting tests";letflow1=execute_test_functionstestsmanflowinPost.returnflow1|>OptionExt.return|S_assert(cond)->assumecond~fthen:(funsafe_flow->safe_assert_checkcond.erangemansafe_flow|>Post.return)~felse:(funfail_flow->raise_assert_failcondmanfail_flow|>Post.return)manflow|>OptionExt.return|S_satisfy(cond)->man.exec(mk_assumecondstmt.srange)flow>>%?funflow'->ifnot@@man.lattice.is_bottom@@Flow.getT_curman.latticeflow'thensafe_assert_checkcond.erangemanflow|>Post.return|>OptionExt.returnelseraise_assert_failcondmanflow|>Post.return|>OptionExt.return|_->Noneletevalexpmanflow=Noneletaskquerymanflow=Noneletprint_exprmanflowprinterexp=()endlet()=register_stateless_domain(moduleDomain)