File: crowbar_test.ml

package info (click to toggle)
ocaml-patch 3.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 384 kB
  • sloc: ml: 2,379; sh: 71; makefile: 3
file content (159 lines) | stat: -rw-r--r-- 4,524 bytes parent folder | download
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
159
(** USAGE:

    This test works by generating two files (source and target),
    diffing them using the system `diff -u` command,
    applying our Patch code to the source file,
    and checking that we get the target back.

    Counter-examples will give you the source and target file.

    From the root repository, run

       dune exec test/crowbar_test.exe

    to get quicheck-like (blackbox) fuzzing, and

       mkdir -p /tmp/input
       mkdir -p /tmp/output
       echo foo > /tmp/input/test
       dune build test/crowbar_test.exe
       afl-fuzz -i /tmp/input -o /tmp/output dune exec test/crowbar_test.exe @@

    for AFL-full (greybox) fuzzing.

    If you find a counter-example, you can use src/patch_command
    to reproduce the issue. For example, if the quickcheck mode tells you:

    > patch: ....
    > patch: FAIL
    >
    > When given the input:
    >
    >     ["\nx"; "\n"]
    >
    > the test failed:
    >
    >     "" != "\n"
    >

    You can run

      echo -n -e "\nx" > file1
      echo -n -e "\n" > file2
      diff -u file1 file2 > diff
      patch file1 diff -o file2-std-patch
      dune exec src/patch_command.exe -- file1 diff -o file2-our-patch
      diff file2-our-patch file2-std-patch
      rm file1 file2 diff file2-std-patch file2-our-patch

    to check for yourself.
*)


type line = string
type file = line list

let string_of_file = String.concat ""

module Printer = struct
  open Crowbar
  let line : line printer =
    fun ppf line -> pp ppf "%S" line
  let file : file printer =
    fun ppf file -> pp ppf "%S" (String.concat "" file)
end

module Gen = struct
  open Crowbar
  let char : string gen =
    map [range 25] (fun n -> String.make 1 (char_of_int (int_of_char 'a' + n)))
  let line : line gen =
    with_printer Printer.line @@
    map [list char] (fun s -> String.concat "" (s @ ["\n"]))
  let line_no_eol : line gen =
    with_printer Printer.line @@
    map [list char] (fun s -> String.concat "" s)
  let file : file gen =
    with_printer Printer.file @@
    choose [
      list line;
      map [list line; line_no_eol] (fun lines line -> lines @ [line]);
    ]
end

module IO = struct
  let read input =
    let rec loop buf acc input =
      match input_char input with
      | exception End_of_file ->
        if Buffer.length buf = 0 then List.rev acc
        else List.rev (Buffer.contents buf :: acc)
      | '\n' ->
        Buffer.add_char buf '\n';
        let line = Buffer.contents buf in
        Buffer.clear buf;
        loop buf (line :: acc) input
      | c ->
        Buffer.add_char buf c;
        loop buf acc input
    in
    loop (Buffer.create 80) [] input

  let write output file =
    List.iter (output_string output) file;
    ()

  let with_file_out file k =
    let (path, oc) = Filename.open_temp_file "patch_crowbar" "" in
    let clean () =
      close_out oc;
      Sys.remove path in
    write oc file;
    flush oc;
    match k path with
    | exception exn -> clean (); raise exn
    | res -> clean (); res

  let with_tmp k =
    let path = Filename.temp_file "patch_crowbar_diff" "" in
    let clean () = Sys.remove path in
    match k path with
    | exception exn -> clean (); raise exn
    | res -> clean (); res
end

(** getting a system *diff* from two files *)
let get_diffs (file1 : file) (file2 : file) : file =
  IO.with_file_out file1 @@ fun path1 ->
  IO.with_file_out file2 @@ fun path2 ->
  IO.with_tmp @@ fun path_out ->
  Printf.ksprintf (fun cmd -> ignore (Sys.command cmd))
    "diff -u %S %S > %S" path1 path2 path_out;
  let input = open_in path_out in
  let res = IO.read input in
  close_in input;
  res

let check_Patch file1 file2 =
  let text_diff = string_of_file (get_diffs file1 file2) in
  match Patch.parse ~p:0 text_diff with
  | [] -> Crowbar.check_eq (string_of_file file1) (string_of_file file2)
  | _::_::_ -> Crowbar.fail "not a single diff!"
  | [diff] ->
    let data = string_of_file file1 in
    match Patch.patch ~cleanly:true (Some data) diff with
    | None ->
      let exp = string_of_file file2 in
      Crowbar.fail ("input file\n" ^ data ^ "\ndiff\n" ^ text_diff ^ "\nexpected\n" ^ exp)
    | Some output ->
      let exp = string_of_file file2 in
      if String.equal exp output then
        Crowbar.check_eq
          ~pp:Crowbar.pp_string
          output exp
      else
        Crowbar.fail ("input fileFFF\n" ^ data ^ "FFF\ndiffFFF\n" ^ text_diff ^ "FFF\nexpectedFFF\n" ^ exp ^ "FFF")


let () =
  Crowbar.(add_test ~name:"patch" [Gen.file; Gen.file] check_Patch)