src/Tools/quickcheck.ML
changeset 43020 abb5d1f907e4
parent 43018 121aa59b4d17
child 43024 58150aa44941
equal deleted inserted replaced
43019:619f16bf2150 43020:abb5d1f907e4
     4 Generic counterexample search engine.
     4 Generic counterexample search engine.
     5 *)
     5 *)
     6 
     6 
     7 signature QUICKCHECK =
     7 signature QUICKCHECK =
     8 sig
     8 sig
       
     9   val quickcheckN: string
       
    10   val genuineN: string
       
    11   val noneN: string
       
    12   val unknownN: string
     9   val setup: theory -> theory
    13   val setup: theory -> theory
    10   (* configuration *)
    14   (* configuration *)
    11   val auto: bool Unsynchronized.ref
    15   val auto: bool Unsynchronized.ref
    12   val tester : string Config.T
    16   val tester : string Config.T
    13   val size : int Config.T
    17   val size : int Config.T
    59 end;
    63 end;
    60 
    64 
    61 structure Quickcheck : QUICKCHECK =
    65 structure Quickcheck : QUICKCHECK =
    62 struct
    66 struct
    63 
    67 
       
    68 val quickcheckN = "quickcheck"
       
    69 val quickcheck_paramsN = "quickcheck_params"
       
    70 
       
    71 val genuineN = "genuine"
       
    72 val noneN = "none"
       
    73 val unknownN = "unknown"
       
    74 
    64 (* preferences *)
    75 (* preferences *)
    65 
    76 
    66 val auto = Unsynchronized.ref false;
    77 val auto = Unsynchronized.ref false;
    67 
    78 
    68 val _ =
    79 val _ =
   470 fun pretty_counterex ctxt auto NONE = Pretty.str (tool_name auto ^ " found no counterexample.")
   481 fun pretty_counterex ctxt auto NONE = Pretty.str (tool_name auto ^ " found no counterexample.")
   471   | pretty_counterex ctxt auto (SOME (cex, eval_terms)) =
   482   | pretty_counterex ctxt auto (SOME (cex, eval_terms)) =
   472       Pretty.chunks ((Pretty.str (tool_name auto ^ " found a counterexample:\n") ::
   483       Pretty.chunks ((Pretty.str (tool_name auto ^ " found a counterexample:\n") ::
   473         map (fn (s, t) =>
   484         map (fn (s, t) =>
   474           Pretty.block [Pretty.str (s ^ " ="), Pretty.brk 1, Syntax.pretty_term ctxt t]) (rev cex))
   485           Pretty.block [Pretty.str (s ^ " ="), Pretty.brk 1, Syntax.pretty_term ctxt t]) (rev cex))
   475         @ (Pretty.str ("Evaluated terms:\n") ::
   486         @ (if null eval_terms then []
   476         map (fn (t, u) =>
   487            else (Pretty.str ("Evaluated terms:\n") ::
   477           Pretty.block [Syntax.pretty_term ctxt t, Pretty.str " =", Pretty.brk 1, Syntax.pretty_term ctxt u])
   488              map (fn (t, u) =>
   478           (rev eval_terms)));
   489                Pretty.block [Syntax.pretty_term ctxt t, Pretty.str " =", Pretty.brk 1, Syntax.pretty_term ctxt u])
       
   490                (rev eval_terms))));
   479 
   491 
   480 fun pretty_report (Report {iterations = iterations, raised_match_errors = raised_match_errors,
   492 fun pretty_report (Report {iterations = iterations, raised_match_errors = raised_match_errors,
   481     satisfied_assms = satisfied_assms, positive_concl_tests = positive_concl_tests}) =
   493     satisfied_assms = satisfied_assms, positive_concl_tests = positive_concl_tests}) =
   482   let
   494   let
   483     fun pretty_stat s i = Pretty.block ([Pretty.str (s ^ ": " ^ string_of_int i)])
   495     fun pretty_stat s i = Pretty.block ([Pretty.str (s ^ ": " ^ string_of_int i)])
   508       Pretty.strs (space_explode " " "Please provide an executable interpretation for the locale.")]
   520       Pretty.strs (space_explode " " "Please provide an executable interpretation for the locale.")]
   509   | pretty_counterex_and_reports ctxt auto (result :: _) =
   521   | pretty_counterex_and_reports ctxt auto (result :: _) =
   510     Pretty.chunks (pretty_counterex ctxt auto (response_of result) ::
   522     Pretty.chunks (pretty_counterex ctxt auto (response_of result) ::
   511       (* map (pretty_reports ctxt) (reports_of result) :: *)
   523       (* map (pretty_reports ctxt) (reports_of result) :: *)
   512       (if Config.get ctxt timing then cons (pretty_timings (timings_of result)) else I) [])
   524       (if Config.get ctxt timing then cons (pretty_timings (timings_of result)) else I) [])
   513 
       
   514 (* automatic testing *)
       
   515 
       
   516 fun auto_quickcheck state =
       
   517   let
       
   518     val ctxt = Proof.context_of state;
       
   519     val res =
       
   520       state
       
   521       |> Proof.map_context (Config.put report false #> Config.put quiet true)
       
   522       |> try (test_goal (false, false) ([], []) 1);
       
   523   in
       
   524     case res of
       
   525       NONE => (false, state)
       
   526     | SOME (result :: _) => if found_counterexample result then
       
   527         (true, Proof.goal_message (K (Pretty.chunks [Pretty.str "",
       
   528           Pretty.mark Markup.hilite (pretty_counterex ctxt true (response_of result))])) state)
       
   529       else
       
   530         (false, state)
       
   531   end
       
   532 
       
   533 val setup = Try.register_tool (auto, auto_quickcheck)
       
   534 
   525 
   535 (* Isar commands *)
   526 (* Isar commands *)
   536 
   527 
   537 fun read_nat s = case (Library.read_int o Symbol.explode) s
   528 fun read_nat s = case (Library.read_int o Symbol.explode) s
   538  of (k, []) => if k >= 0 then k
   529  of (k, []) => if k >= 0 then k
   608 
   599 
   609 fun quickcheck args i state = counterexample_of (hd (gen_quickcheck args i state))
   600 fun quickcheck args i state = counterexample_of (hd (gen_quickcheck args i state))
   610 
   601 
   611 fun quickcheck_cmd args i state =
   602 fun quickcheck_cmd args i state =
   612   gen_quickcheck args i (Toplevel.proof_of state)
   603   gen_quickcheck args i (Toplevel.proof_of state)
   613   |> Pretty.writeln o pretty_counterex_and_reports (Toplevel.context_of state) false;
   604   |> Output.urgent_message o Pretty.string_of
       
   605      o pretty_counterex_and_reports (Toplevel.context_of state) false;
   614 
   606 
   615 val parse_arg =
   607 val parse_arg =
   616   Parse.name --
   608   Parse.name --
   617     (Scan.optional (Parse.$$$ "=" |--
   609     (Scan.optional (Parse.$$$ "=" |--
   618       (((Parse.name || Parse.float_number) >> single) ||
   610       (((Parse.name || Parse.float_number) >> single) ||
   620 
   612 
   621 val parse_args =
   613 val parse_args =
   622   Parse.$$$ "[" |-- Parse.list1 parse_arg --| Parse.$$$ "]" || Scan.succeed [];
   614   Parse.$$$ "[" |-- Parse.list1 parse_arg --| Parse.$$$ "]" || Scan.succeed [];
   623 
   615 
   624 val _ =
   616 val _ =
   625   Outer_Syntax.command "quickcheck_params" "set parameters for random testing" Keyword.thy_decl
   617   Outer_Syntax.command quickcheck_paramsN "set parameters for random testing" Keyword.thy_decl
   626     (parse_args >> (fn args => Toplevel.theory (quickcheck_params_cmd args)));
   618     (parse_args >> (fn args => Toplevel.theory (quickcheck_params_cmd args)));
   627 
   619 
   628 val _ =
   620 val _ =
   629   Outer_Syntax.improper_command "quickcheck" "try to find counterexample for subgoal" Keyword.diag
   621   Outer_Syntax.improper_command quickcheckN "try to find counterexample for subgoal" Keyword.diag
   630     (parse_args -- Scan.optional Parse.nat 1
   622     (parse_args -- Scan.optional Parse.nat 1
   631       >> (fn (args, i) => Toplevel.no_timing o Toplevel.keep (quickcheck_cmd args i)));
   623       >> (fn (args, i) => Toplevel.no_timing o Toplevel.keep (quickcheck_cmd args i)));
   632 
   624 
       
   625 (* automatic testing *)
       
   626 
       
   627 fun try_quickcheck auto state =
       
   628   let
       
   629     val ctxt = Proof.context_of state;
       
   630     val i = 1;
       
   631     val res =
       
   632       state
       
   633       |> Proof.map_context (Config.put report false #> Config.put quiet true)
       
   634       |> try (test_goal (false, false) ([], []) i);
       
   635   in
       
   636     case res of
       
   637       NONE => (unknownN, state)
       
   638     | SOME results =>
       
   639         let
       
   640           val msg = pretty_counterex_and_reports ctxt auto results
       
   641                     |> not auto ? tap (Output.urgent_message o Pretty.string_of)
       
   642         in
       
   643           if exists found_counterexample results then
       
   644             (genuineN,
       
   645              state |> (if auto then
       
   646                          Proof.goal_message (K (Pretty.chunks [Pretty.str "",
       
   647                              Pretty.mark Markup.hilite msg]))
       
   648                        else
       
   649                          I))
       
   650           else
       
   651             (noneN, state)
       
   652         end
       
   653   end
       
   654   |> `(fn (outcome_code, _) => outcome_code = genuineN)
       
   655 
       
   656 val setup = Try.register_tool (quickcheckN, (auto, try_quickcheck))
       
   657 
   633 end;
   658 end;
   634 
   659 
   635 
       
   636 val auto_quickcheck = Quickcheck.auto;
   660 val auto_quickcheck = Quickcheck.auto;