Summary: Given a difference between two files, return the relevant lines in the new file; a line is relevant when a change took place in it, or nearby. To generate a valid input for this parser, use unix-diff command with the following formatter arguments: ``` diff --unchanged-line-format="U" --old-line-format="O" --new-line-format="N" File1 File2 ``` Reviewed By: ezgicicek Differential Revision: D7385267 fbshipit-source-id: 0cc3143master
parent
b6c8766b11
commit
49b4a26a93
@ -0,0 +1,90 @@
|
||||
(*
|
||||
* Copyright (c) 2018 - present Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
module UnixDiff = struct
|
||||
type t = Unchanged | New | Old [@@deriving compare]
|
||||
|
||||
let equal = [%compare.equal : t]
|
||||
|
||||
let directive_of_char c =
|
||||
match c with
|
||||
| 'U' ->
|
||||
Unchanged
|
||||
| 'N' ->
|
||||
New
|
||||
| 'O' ->
|
||||
Old
|
||||
| _ ->
|
||||
Logging.die Logging.UserError "Unexpected char in input sequence. Failed parsing"
|
||||
|
||||
|
||||
let process_raw_directives in_str =
|
||||
if String.is_empty in_str then [] else String.to_list in_str |> List.map ~f:directive_of_char
|
||||
|
||||
|
||||
let pp fmt d =
|
||||
match d with
|
||||
| Unchanged ->
|
||||
Format.fprintf fmt "U"
|
||||
| New ->
|
||||
Format.fprintf fmt "N"
|
||||
| Old ->
|
||||
Format.fprintf fmt "O"
|
||||
|
||||
|
||||
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct
|
||||
type nonrec t = t = Unchanged | New | Old
|
||||
|
||||
let equal = equal
|
||||
|
||||
let process_raw_directives = process_raw_directives
|
||||
|
||||
let pp = pp
|
||||
end
|
||||
end
|
||||
|
||||
let parse_directives directives =
|
||||
let rec aux ~d ~lines ~line_ptr ~pred_is_old =
|
||||
(* O does not move the line-pointer *)
|
||||
(* N moves the line-pointer and marks the line as affected *)
|
||||
(* U moves the line-pointer, and marks the line as affected ONLY if it is preceded by O *)
|
||||
match d with
|
||||
| [] ->
|
||||
List.rev lines
|
||||
| UnixDiff.Old :: ds ->
|
||||
aux ~d:ds ~lines ~line_ptr ~pred_is_old:true
|
||||
| UnixDiff.New :: ds ->
|
||||
aux ~d:ds ~lines:(line_ptr :: lines) ~line_ptr:(line_ptr + 1) ~pred_is_old:false
|
||||
| UnixDiff.Unchanged :: ds ->
|
||||
let lines' = if pred_is_old then line_ptr :: lines else lines in
|
||||
aux ~d:ds ~lines:lines' ~line_ptr:(line_ptr + 1) ~pred_is_old:false
|
||||
in
|
||||
if List.is_empty directives then (* handle the case where both files are empty *)
|
||||
[]
|
||||
else if (* handle the case where the new-file is empty *)
|
||||
List.for_all ~f:(UnixDiff.equal UnixDiff.Old) directives
|
||||
then [1]
|
||||
else
|
||||
let pred_is_old, directives' =
|
||||
match directives with UnixDiff.Old :: ds -> (true, ds) | _ -> (false, directives)
|
||||
in
|
||||
aux ~d:directives' ~lines:[] ~line_ptr:1 ~pred_is_old
|
||||
|
||||
|
||||
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct
|
||||
let parse_directives = parse_directives
|
||||
end
|
||||
|
||||
(** Given a difference between two files, return the relevant lines in the new file; a line is
|
||||
relevant when a change took place in it, or nearby. To generate a valid input for this
|
||||
parser, use unix-diff command with the following formatter arguments:
|
||||
diff --unchanged-line-format="U" --old-line-format="O" --new-line-format="N" File1 File2 *)
|
||||
let parse_unix_diff str = UnixDiff.process_raw_directives str |> parse_directives
|
@ -0,0 +1,30 @@
|
||||
(*
|
||||
* Copyright (c) 2018 - present Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*)
|
||||
|
||||
module UnixDiff : sig
|
||||
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig
|
||||
type t = Unchanged | New | Old
|
||||
|
||||
val equal : t -> t -> bool
|
||||
|
||||
val process_raw_directives : string -> t list
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
end
|
||||
end
|
||||
|
||||
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig
|
||||
val parse_directives : UnixDiff.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.t list -> int list
|
||||
end
|
||||
|
||||
val parse_unix_diff : string -> int list
|
||||
(** Given a difference between two files, return the relevant lines in the new file; a line is
|
||||
relevant when a change took place in it, or nearby. To generate a valid input for this
|
||||
parser, use unix-diff command with the following formatter arguments:
|
||||
diff --unchanged-line-format="U" --old-line-format="O" --new-line-format="N" File1 File2 *)
|
@ -0,0 +1,267 @@
|
||||
(*
|
||||
* Copyright (c) 2018 - present Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
open OUnit2
|
||||
module UnixDiffTest = FileDiff.UnixDiff.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY
|
||||
|
||||
let test_unixdiff_process_raw_directives_with_valid_input =
|
||||
let create_test input expected _ =
|
||||
let found = UnixDiffTest.process_raw_directives input in
|
||||
let pp_diff fmt (expected, actual) =
|
||||
let expected_str = Format.asprintf "%a" (Pp.seq ~sep:"" UnixDiffTest.pp) expected in
|
||||
let actual_str = Format.asprintf "%a" (Pp.seq ~sep:"" UnixDiffTest.pp) actual in
|
||||
Format.fprintf fmt "Expected: '%s', found: '%s'" expected_str actual_str
|
||||
in
|
||||
assert_equal ~cmp:(List.equal ~equal:UnixDiffTest.equal) ~pp_diff expected found
|
||||
in
|
||||
[ ( "test_unixdiff_process_raw_directives_1"
|
||||
, "UOOU"
|
||||
, UnixDiffTest.([Unchanged; Old; Old; Unchanged]) )
|
||||
; ("test_unixdiff_process_raw_directives_2", "", []) ]
|
||||
|> List.map ~f:(fun (name, test_input, expected_output) ->
|
||||
name >:: create_test test_input expected_output )
|
||||
|
||||
|
||||
let test_unixdiff_process_raw_directives_with_invalid_input =
|
||||
let create_test input expected_exception _ =
|
||||
let run () = UnixDiffTest.process_raw_directives input in
|
||||
assert_raises expected_exception run
|
||||
in
|
||||
[ ( "test_unixdiff_process_raw_directives_1"
|
||||
, "U OOU"
|
||||
, Logging.InferUserError "Unexpected char in input sequence. Failed parsing" )
|
||||
; ( "test_unixdiff_process_raw_directives_2"
|
||||
, "UZ"
|
||||
, Logging.InferUserError "Unexpected char in input sequence. Failed parsing" )
|
||||
; ( "test_unixdiff_process_raw_directives_3"
|
||||
, "UU "
|
||||
, Logging.InferUserError "Unexpected char in input sequence. Failed parsing" )
|
||||
; ( "test_unixdiff_process_raw_directives_4"
|
||||
, " U"
|
||||
, Logging.InferUserError "Unexpected char in input sequence. Failed parsing" ) ]
|
||||
|> List.map ~f:(fun (name, test_input, expected_exception) ->
|
||||
name >:: create_test test_input expected_exception )
|
||||
|
||||
|
||||
let test_unixdiff_pp =
|
||||
let create_test input expected _ =
|
||||
let found = Format.asprintf "%a" (Pp.seq ~sep:"" UnixDiffTest.pp) input in
|
||||
let pp_diff fmt (expected, actual) =
|
||||
Format.fprintf fmt "Expected: '%s', found: '%s'" expected actual
|
||||
in
|
||||
assert_equal ~cmp:String.equal ~pp_diff expected found
|
||||
in
|
||||
[ ("test_unixdiff_pp_1", UnixDiffTest.([Unchanged; Old; Old; Unchanged]), "UOOU")
|
||||
; ("test_unixdiff_pp_2", [], "") ]
|
||||
|> List.map ~f:(fun (name, test_input, expected_output) ->
|
||||
name >:: create_test test_input expected_output )
|
||||
|
||||
|
||||
module FileDiffTest = FileDiff.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY
|
||||
|
||||
let u length = List.init ~f:(fun _ -> UnixDiffTest.Unchanged) length
|
||||
|
||||
let n length = List.init ~f:(fun _ -> UnixDiffTest.New) length
|
||||
|
||||
let o length = List.init ~f:(fun _ -> UnixDiffTest.Old) length
|
||||
|
||||
let test_parse_directives_with_valid_input =
|
||||
let create_test input expected _ =
|
||||
let found = FileDiffTest.parse_directives input in
|
||||
let pp_diff fmt (expected, actual) =
|
||||
let expected_str = Format.asprintf "%a" (Pp.seq ~sep:", " Format.pp_print_int) expected in
|
||||
let actual_str = Format.asprintf "%a" (Pp.seq ~sep:", " Format.pp_print_int) actual in
|
||||
Format.fprintf fmt "Expected: '%s', found: '%s'" expected_str actual_str
|
||||
in
|
||||
assert_equal ~cmp:(List.equal ~equal:Int.equal) ~pp_diff expected found
|
||||
in
|
||||
[ (*
|
||||
=== test1 ===
|
||||
File1 and File2 are empty
|
||||
*)
|
||||
("test_parse_directives_with_valid_input_1", [], [])
|
||||
(*
|
||||
=== test2 ===
|
||||
File1 File2
|
||||
a x
|
||||
b
|
||||
c
|
||||
d
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_2", o 4 @ n 1, [1])
|
||||
(*
|
||||
=== test3 ===
|
||||
File1 File2
|
||||
a x
|
||||
v b
|
||||
b c
|
||||
c Z
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_3", o 2 @ n 1 @ u 2 @ n 1, [1; 4])
|
||||
(*
|
||||
=== test4 ===
|
||||
File1 File2
|
||||
a w
|
||||
b x
|
||||
c y
|
||||
z
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_4", o 3 @ n 4, [1; 2; 3; 4])
|
||||
(*
|
||||
=== test5 ===
|
||||
File1 File2
|
||||
a a
|
||||
b b
|
||||
c c
|
||||
d d
|
||||
e e
|
||||
w
|
||||
x
|
||||
y
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_5", u 5 @ n 3, [6; 7; 8])
|
||||
(*
|
||||
=== test6 ===
|
||||
File1 File2
|
||||
a a
|
||||
b b
|
||||
c c
|
||||
d d
|
||||
e e
|
||||
f i
|
||||
g
|
||||
h
|
||||
i
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_6", u 5 @ o 3 @ u 1, [6])
|
||||
(*
|
||||
=== test7 ===
|
||||
File1 File2
|
||||
a
|
||||
b
|
||||
c
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_7", o 3, [1])
|
||||
(*
|
||||
=== test8 ===
|
||||
File1 File2
|
||||
a a
|
||||
b d
|
||||
c x
|
||||
d y
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_8", u 1 @ o 2 @ u 1 @ n 2, [2; 3; 4])
|
||||
(*
|
||||
=== test9 ===
|
||||
File1 File2
|
||||
a d
|
||||
b x
|
||||
c y
|
||||
d z
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_9", o 3 @ u 1 @ n 3, [1; 2; 3; 4])
|
||||
(*
|
||||
=== test10 ===
|
||||
File1 File2
|
||||
a a
|
||||
b x
|
||||
c d
|
||||
d y
|
||||
e z
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_10", u 1 @ o 2 @ n 1 @ u 1 @ o 1 @ n 2, [2; 4; 5])
|
||||
(*
|
||||
=== test11 ===
|
||||
File1 File2
|
||||
a a
|
||||
b b
|
||||
c x
|
||||
d c
|
||||
e y
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_11", u 2 @ n 1 @ u 1 @ o 2 @ n 1, [3; 5])
|
||||
; ( "test_parse_directives_with_valid_input_12"
|
||||
, o 1 @ n 1 @ u 6 @ o 2 @ n 2 @ u 5 @ o 2 @ n 2 @ u 244 @ o 12 @ u 3 @ o 1 @ n 1 @ u 3
|
||||
, [1; 8; 9; 15; 16; 261; 264] )
|
||||
(*
|
||||
=== test13 ===
|
||||
File1 File2
|
||||
a a
|
||||
b b
|
||||
c c
|
||||
d x
|
||||
e y
|
||||
d
|
||||
e
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_13", u 3 @ n 2 @ u 2, [4; 5])
|
||||
(*
|
||||
=== test14 ===
|
||||
File1 File2
|
||||
a e
|
||||
b w
|
||||
c x
|
||||
d y
|
||||
e z
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_14", o 4 @ u 1 @ n 4, [1; 2; 3; 4; 5])
|
||||
(*
|
||||
=== test15 ===
|
||||
File1 File2
|
||||
a a
|
||||
b x
|
||||
c
|
||||
d
|
||||
e
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_15", u 1 @ o 4 @ n 1, [2])
|
||||
(*
|
||||
=== test16 ===
|
||||
File1 File2
|
||||
a x
|
||||
b e
|
||||
c
|
||||
d
|
||||
e
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_16", o 4 @ n 1 @ u 1, [1])
|
||||
(*
|
||||
=== test17 ===
|
||||
File1 File2
|
||||
a x
|
||||
v b
|
||||
b c
|
||||
c
|
||||
*)
|
||||
; ("test_parse_directives_with_valid_input_17", o 2 @ n 1 @ u 2, [1]) ]
|
||||
|> List.map ~f:(fun (name, test_input, expected_output) ->
|
||||
name >:: create_test test_input expected_output )
|
||||
|
||||
|
||||
let test_parse_unix_diff_with_valid_input =
|
||||
let create_test input expected _ =
|
||||
let found = FileDiff.parse_unix_diff input in
|
||||
let pp_diff fmt (expected, actual) =
|
||||
let expected_str = Format.asprintf "%a" (Pp.seq ~sep:", " Format.pp_print_int) expected in
|
||||
let actual_str = Format.asprintf "%a" (Pp.seq ~sep:", " Format.pp_print_int) actual in
|
||||
Format.fprintf fmt "Expected: '%s', found: '%s'" expected_str actual_str
|
||||
in
|
||||
assert_equal ~cmp:(List.equal ~equal:Int.equal) ~pp_diff expected found
|
||||
in
|
||||
[("test_parse_unix_diff_1", "OONUU", [1]); ("test_parse_unix_diff_2", "UOONUONN", [2; 4; 5])]
|
||||
|> List.map ~f:(fun (name, test_input, expected_output) ->
|
||||
name >:: create_test test_input expected_output )
|
||||
|
||||
|
||||
let tests =
|
||||
"filediff"
|
||||
>::: test_unixdiff_process_raw_directives_with_valid_input
|
||||
@ test_unixdiff_process_raw_directives_with_invalid_input @ test_unixdiff_pp
|
||||
@ test_parse_directives_with_valid_input @ test_parse_unix_diff_with_valid_input
|
Loading…
Reference in new issue