Skip to content
This repository
tree: fec82387ee
Fetching contributors…

Cannot retrieve contributors at this time

file 158 lines (139 sloc) 5.798 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
(* Yoann Padioleau
*
* Copyright (C) 2010 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)

open Common

module J = Json_type

(*****************************************************************************)
(* Prelude *)
(*****************************************************************************)

(*
* The goal of this module is to provide data structures that can be
* used to mimic the Microsoft Echelon[1] project which given a patch
* try to run the most relevant tests that could be affected by the
* patch. It is probably easier in interpreted languages such as PHP which
* contain simple tracers/profilers.
*
* We can even run the tests and says whether the new code has
* been covered (like in MySql test infrastructure).
*
* For now we just provide types for a mapping from
* a source code file to a list of relevant test files.
*
* References:
* [1] http://research.microsoft.com/apps/pubs/default.aspx?id=69911
*)

(*****************************************************************************)
(* Types *)
(*****************************************************************************)

(* relevant test files exercising source, with term-frequency of
* file in the test *)
type tests_coverage = (Common.filename, tests_score) Common.assoc
 and tests_score = (Common.filename * float) list
 (* with tarzan *)

(* Note that xdebug by default does not trace assignements but only
* function and method calls, which mean the list of lines returned
* is an under-approximation. We compensate such an approximation by
* also computing the static set of function/method calls so that
* a coverage percentage can be computed.
*
* update: with hphpi tracer, we actually also cover assignement and
* this type is actually independent of such design decision.
* It's line-based though, so don't expect complex path coverage
* or MCDC stuff. Just simple line coverage ...
*)
type lines_coverage = (Common.filename, file_lines_coverage) Common.assoc
 and file_lines_coverage = {
   covered_sites: int list;
   all_sites: int list;
 }
 (* with tarzan *)

(*****************************************************************************)
(* String of, json, etc *)
(*****************************************************************************)

(* This helps generates a coverage file that 'arc unit' can read *)
let (json_of_tests_coverage: tests_coverage -> J.json_type) = fun cov ->
  J.Object (cov |> List.map (fun (cover_file, tests_score) ->
    cover_file,
    J.Array (tests_score |> List.map (fun (test_file, score) ->
      J.Array [J.String test_file; J.String (spf "%.3f" score)]
    ))
  ))

(* todo: should be autogenerated by ocamltarzan *)
let (tests_coverage_of_json: J.json_type -> tests_coverage) = fun j ->
  match j with
  | J.Object (xs) ->
      xs |> List.map (fun (cover_file, tests_score) ->
        cover_file,
        match tests_score with
        | J.Array zs ->
            zs |> List.map (fun test_file_score_pair ->
              (match test_file_score_pair with
              | J.Array [J.String test_file; J.String str_score] ->
                  test_file, float_of_string str_score
                    
              | _ -> failwith "Bad json, tests_coverage_of_json"
              )
            )
        | _ -> failwith "Bad json, tests_coverage_of_json"
      )
  | _ -> failwith "Bad json, tests_coverage_of_json"

(* todo: should be autogenerated by ocamltarzan *)
let (json_of_lines_coverage: lines_coverage -> J.json_type) = fun cov ->
  J.Object (cov |> List.map (fun (file, cover) ->
    file,
    J.Object ([
      (* I use short fieldnames to avoid generating a huge JSON file.
*)
      "cov", J.Array (cover.covered_sites |> List.map (fun l -> J.Int l));
      "all", J.Array (cover.all_sites |> List.map (fun l -> J.Int l));
    ])
  ))

let (lines_coverage_of_json: J.json_type -> lines_coverage) = fun j ->
  match j with
  | J.Object (xs) ->
      xs |> List.map (fun (file, cover) ->
        file,
        match cover with
        | J.Object ([
            "cov", J.Array covered_lines;
            "all", J.Array call_sites;
          ]) ->
            {
              covered_sites =
                covered_lines |> List.map (function
                | J.Int l -> l
                | _ -> failwith "Bad json, files_coverage_of_json"
                );
              all_sites =
                call_sites |> List.map (function
                | J.Int l -> l
                | _ -> failwith "Bad json, files_coverage_of_json"
                );
            }
        | _ -> failwith "Bad json, files_coverage_of_json"
      )
  | _ -> failwith "Bad json, files_coverage_of_json"


let (save_tests_coverage: tests_coverage -> Common.filename -> unit) =
 fun cov file ->
   cov |> json_of_tests_coverage |> Json_out.string_of_json
   |> Common.write_file ~file

let (load_tests_coverage: Common.filename -> tests_coverage) =
 fun file ->
   file |> Json_in.load_json |> tests_coverage_of_json


let (save_lines_coverage: lines_coverage -> Common.filename -> unit) =
 fun cov file ->
   cov |> json_of_lines_coverage |> Json_out.string_of_json
   |> Common.write_file ~file
   
let (load_lines_coverage: Common.filename -> lines_coverage) =
 fun file ->
   file |> Json_in.load_json |> lines_coverage_of_json
Something went wrong with that request. Please try again.