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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
|
open Stdune
let () = Dune_tests_common.init ()
(* Basic example adding and removing a line. *)
let basic =
{|
diff --git a/foo.ml b/foo.ml
index b69a69a5a..ea988f6bd 100644
--- a/foo.ml
+++ b/foo.ml
@@ -1,1 +1,1 @@
-This is wrong
+This is right
|}
;;
(* Example adding and removing a line in a file in a subdirectory. *)
let subdirectory =
{|
diff --git a/dir/foo.ml b/dir/foo.ml
index b69a69a5a..ea988f6bd 100644
--- a/dir/foo.ml
+++ b/dir/foo.ml
@@ -1,1 +1,1 @@
-This is wrong
+This is right
|}
;;
(* Previous two example combined into a single patch. *)
let combined = String.concat ~sep:"\n" [ basic; subdirectory ]
(* Example adding a new file. *)
let new_file =
{|
diff --git a/foo.ml b/foo.ml
new file mode 100644
index 000000000..ea988f6bd
--- /dev/null
+++ b/foo.ml
@@ -0,0 +1,2 @@
+This is right
+
|}
;;
(* Example deleting an existing file. *)
let delete_file =
{|
diff --git a/foo.ml b/foo.ml
deleted file mode 100644
index ea988f6bd..000000000
--- a/foo.ml
+++ /dev/null
@@ -1,1 +0,0 @@
-This is wrong
|}
;;
(* Use GNU diff 'unified' format instead of 'git diff' *)
let unified =
{|
diff -u a/foo.ml b/foo.ml
--- a/foo.ml 2024-08-29 17:37:53.114980665 +0200
+++ b/foo.ml 2024-08-29 17:38:00.243088256 +0200
@@ -1 +1 @@
-This is wrong
+This is right
|}
;;
let no_prefix =
{|
--- foo.ml 2024-08-29 17:37:53.114980665 +0200
+++ foo.ml 2024-08-29 17:38:00.243088256 +0200
@@ -1 +1 @@
-This is wrong
+This is right
|}
;;
let random_prefix =
{|
diff -u bar/foo.ml baz/foo.ml
--- bar/foo.ml 2024-08-29 17:37:53.114980665 +0200
+++ baz/foo.ml 2024-08-29 17:38:00.243088256 +0200
@@ -1 +1 @@
-This is wrong
+This is right
|}
;;
(* The file is called "foo bar" *)
let spaces =
{|
diff --git a/foo bar b/foo bar
index ef00db3..88adca3 100644
--- a/foo bar
+++ b/foo bar
@@ -1 +1 @@
-This is wrong.
+This is right.
|}
;;
(* The file is called "foo bar" but in unified diff its quoted *)
let unified_spaces =
{|
--- "a/foo bar" 2024-09-04 10:56:24.139293679 +0200
+++ "b/foo bar" 2024-09-04 10:56:12.519195763 +0200
@@ -1 +1 @@
-This is wrong.
+This is right.
|}
;;
(* Testing the patch action *)
include struct
open Dune_engine
module Action = Action
module Display = Display
module Process = Process
module Scheduler = Scheduler
end
let create_files =
List.iter ~f:(fun (f, contents) ->
ignore
(Fpath.mkdir_p
(Path.Local.of_string f
|> Path.Local.parent
|> Option.value ~default:(Path.Local.of_string ".")
|> Path.Local.to_string));
Io.String_path.write_file f contents)
;;
let test files (patch, patch_contents) =
let dir = Temp.create Dir ~prefix:"dune" ~suffix:"patch_test" in
let display = Display.Quiet in
Sys.chdir (Path.to_string dir);
let patch_file = Path.append_local dir (Path.Local.of_string patch) in
let config =
{ Scheduler.Config.concurrency = 1
; stats = None
; print_ctrl_c_warning = false
; watch_exclusions = []
}
in
Scheduler.Run.go
config
~timeout_seconds:5.0
~file_watcher:No_watcher
~on_event:(fun _ _ -> ())
@@ fun () ->
let open Fiber.O in
let* () = Fiber.return @@ create_files ((patch, patch_contents) :: files) in
Dune_patch.For_tests.exec display ~patch:patch_file ~dir ~stderr:Process.Io.stderr
;;
let check path =
match (Unix.stat path).st_kind with
| S_REG -> Io.String_path.cat path
| _ -> failwith "Not a regular file"
| exception Unix.Unix_error (Unix.ENOENT, _, _) -> printfn "File %s not found" path
;;
let%expect_test "patching a file" =
test [ "foo.ml", "This is wrong\n" ] ("foo.patch", basic);
check "foo.ml";
[%expect
{|
This is right |}]
;;
let%expect_test "patching a file in a subdirectory" =
test [ "dir/foo.ml", "This is wrong\n" ] ("foo.patch", subdirectory);
check "dir/foo.ml";
[%expect
{|
This is right |}]
;;
let%expect_test "patching two files with a single patch" =
test
[ "foo.ml", "This is wrong\n"; "dir/foo.ml", "This is wrong\n" ]
("foo.patch", combined);
check "foo.ml";
[%expect
{|
This is right |}]
;;
let%expect_test "patching a new file" =
test [] ("foo.patch", new_file);
check "foo.ml";
[%expect
{|
This is right |}]
;;
let () = Dune_util.Report_error.report_backtraces true
let%expect_test "patching a deleted file" =
let filename = "foo.ml" in
test [ filename, "This is wrong\n" ] ("foo.patch", delete_file);
(* Different implementations of the patch command behave differently when the
patch specifies deleting a file: *)
match Unix.stat filename with
| { st_kind = S_REG; st_size; _ } ->
(* Some implementations of patch (e.g. the patch that ships with macOS
15.1) do not delete files, and instead truncate them to 0 length. If
the file still exists after applying the patch, assert that it is now
empty. *)
assert (st_size == 0)
| _ -> failwith "Not a regular file"
| exception Unix.Unix_error (Unix.ENOENT, _, _) ->
(* Most implementations of patch will delete the file. *)
()
;;
let undo_breaks =
String.map ~f:(function
| '\n' -> ' '
| c -> c)
;;
let rsplit2_exn s ~on =
match String.rsplit2 s ~on with
| Some s -> s
| None -> Code_error.raise "rsplit2_exn" [ "s", String s; "on", Char on ]
;;
let normalize_error_path s =
let s = undo_breaks s in
let location, reason = rsplit2_exn s ~on:':' in
let prefix, path = String.lsplit2_exn location ~on:' ' in
let path = Filename.basename path in
sprintf "%s %s:%s" prefix path reason
;;
let%expect_test "Using a patch from 'diff' with a timestamp" =
test [ "foo.ml", "This is wrong\n" ] ("foo.patch", unified);
check "foo.ml";
[%expect
{|
This is right |}]
;;
let%expect_test "patching a file without prefix" =
test [ "foo.ml", "This is wrong\n" ] ("foo.patch", no_prefix);
check "foo.ml";
[%expect {| This is right |}]
;;
let%expect_test "patching files with freestyle prefix" =
test [ "foo.ml", "This is wrong\n" ] ("foo.patch", random_prefix);
check "foo.ml";
[%expect {| This is right |}]
;;
let%expect_test "patching files with spaces" =
try
test [ "foo bar", "This is wrong\n" ] ("foo.patch", spaces);
check "foo bar";
[%expect.unreachable]
with
| Dune_util.Report_error.Already_reported ->
print_endline @@ normalize_error_path [%expect.output];
[%expect {| Error: foo: No such file or directory |}]
;;
let%expect_test "patching files with (unified) spaces" =
try
test [ "foo bar", "This is wrong\n" ] ("foo.patch", unified_spaces);
check "foo bar";
[%expect.unreachable]
with
| Dune_util.Report_error.Already_reported ->
print_endline @@ normalize_error_path [%expect.output];
[%expect {| Error: foo: No such file or directory |}]
;;
|