| 1 | %%% Copyright (C) Dominic Williams, Nicolas Charpentier |
|---|
| 2 | %%% All rights reserved. |
|---|
| 3 | %%% See file COPYING. |
|---|
| 4 | |
|---|
| 5 | -module (directory_watcher_test). |
|---|
| 6 | -test (exports). |
|---|
| 7 | -export ([tests_from_empty/0]). |
|---|
| 8 | -export ([tests_from_non_existent/0]). |
|---|
| 9 | -export ([tests_with_several/0]). |
|---|
| 10 | -export ([bad_symlinks_are_ignored/0]). |
|---|
| 11 | -export ([recursive/0]). |
|---|
| 12 | -export ([directories_can_be_removed/0]). |
|---|
| 13 | -export ([directories_can_be_removed_when_recursive/0]). |
|---|
| 14 | -export ([change_from_directory_to_file/0]). |
|---|
| 15 | -export ([change_from_directory_to_file_when_recursive/0]). |
|---|
| 16 | -export ([change_from_file_to_directory/0]). |
|---|
| 17 | -export ([change_from_file_to_directory_when_recursive/0]). |
|---|
| 18 | -export ([remove_and_add_in_same_check/0]). |
|---|
| 19 | -export ([insensitive_to_cwd/0]). |
|---|
| 20 | -export ([can_replace_all_in_same_check/0]). |
|---|
| 21 | -export ([can_find_two_at_a_time/0]). |
|---|
| 22 | -export([tree/0]). |
|---|
| 23 | |
|---|
| 24 | tests_from_empty () -> |
|---|
| 25 | ok = fixtures: use_tree ([], fun tests_from_empty/2). |
|---|
| 26 | |
|---|
| 27 | tests_from_non_existent () -> |
|---|
| 28 | Dir = fixtures: temporary_pathname (), |
|---|
| 29 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 30 | {directory, Dir, {error, enoent}} = receive_one (), |
|---|
| 31 | Watcher ! check, |
|---|
| 32 | {directory, Dir, {error, enoent}} = receive_one (), |
|---|
| 33 | ok = file: make_dir (Dir), |
|---|
| 34 | Watcher ! check, |
|---|
| 35 | timeout = receive_one (), |
|---|
| 36 | ok = file: del_dir (Dir), |
|---|
| 37 | Watcher ! {self (), stop}, |
|---|
| 38 | ok. |
|---|
| 39 | |
|---|
| 40 | tests_with_several () -> |
|---|
| 41 | Tree = [{file, "foo.txt", "Hello"}, |
|---|
| 42 | {file, "bar.txt", "G'day"}, |
|---|
| 43 | {file, "toto.erl", "-module(toto)."}], |
|---|
| 44 | ok = fixtures: use_tree (Tree, fun tests_with_several/2). |
|---|
| 45 | |
|---|
| 46 | bad_symlinks_are_ignored () -> |
|---|
| 47 | ok = fixtures: use_tree ([], fun bad_symlinks_are_ignored/2). |
|---|
| 48 | |
|---|
| 49 | recursive () -> |
|---|
| 50 | ok = fixtures: use_tree (tree (), fun recursive/2). |
|---|
| 51 | |
|---|
| 52 | directories_can_be_removed () -> |
|---|
| 53 | ok = fixtures: use_tree (tree (), fun directories_can_be_removed/2). |
|---|
| 54 | |
|---|
| 55 | directories_can_be_removed_when_recursive () -> |
|---|
| 56 | F = fun directories_can_be_removed_when_recursive/2, |
|---|
| 57 | ok = fixtures: use_tree (tree (), F). |
|---|
| 58 | |
|---|
| 59 | change_from_directory_to_file () -> |
|---|
| 60 | Tree = [{directory, "foo", []}], |
|---|
| 61 | ok = fixtures: use_tree (Tree, fun change_from_directory_to_file/2). |
|---|
| 62 | |
|---|
| 63 | change_from_file_to_directory () -> |
|---|
| 64 | Tree = [{file, "foo", []}], |
|---|
| 65 | ok = fixtures: use_tree (Tree, fun change_from_file_to_directory/2). |
|---|
| 66 | |
|---|
| 67 | change_from_file_to_directory_when_recursive () -> |
|---|
| 68 | F = fun change_from_file_to_directory_when_recursive/2, |
|---|
| 69 | ok = fixtures: use_tree ([{file, "foo", "yo"}], F). |
|---|
| 70 | |
|---|
| 71 | change_from_directory_to_file_when_recursive () -> |
|---|
| 72 | F = fun change_from_directory_to_file_when_recursive/2, |
|---|
| 73 | ok = fixtures: use_tree (tree (), F). |
|---|
| 74 | |
|---|
| 75 | remove_and_add_in_same_check () -> |
|---|
| 76 | F = fun remove_and_add_in_same_check/2, |
|---|
| 77 | ok = fixtures: use_tree ([{file, "foo", "foo"}], F). |
|---|
| 78 | |
|---|
| 79 | insensitive_to_cwd () -> |
|---|
| 80 | Tree = [{directory, "mydir", []}], |
|---|
| 81 | Fun = fun insensitive_to_cwd/2, |
|---|
| 82 | ok = fixtures: use_tree (Tree, Fun). |
|---|
| 83 | |
|---|
| 84 | insensitive_to_cwd (Root, [{directory, Name, _}]) -> |
|---|
| 85 | Dir = filename: join (Root, Name), |
|---|
| 86 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 87 | receive_all (), |
|---|
| 88 | {ok, Cwd} = file: get_cwd (), |
|---|
| 89 | ok = file: set_cwd (Root), |
|---|
| 90 | Watcher ! check, |
|---|
| 91 | true = is_process_alive (Watcher), |
|---|
| 92 | [] = receive_all (), |
|---|
| 93 | ok = file: set_cwd (Cwd), |
|---|
| 94 | ok. |
|---|
| 95 | |
|---|
| 96 | can_replace_all_in_same_check () -> |
|---|
| 97 | Tree = [{file, X, X} || X <- ["foo", "bar", "baz"]], |
|---|
| 98 | F = fun can_replace_all_in_same_check/2, |
|---|
| 99 | ok = fixtures: use_tree (Tree, F). |
|---|
| 100 | |
|---|
| 101 | can_find_two_at_a_time () -> |
|---|
| 102 | F = fun can_find_two_at_a_time/2, |
|---|
| 103 | ok = fixtures: use_tree ([{file, "foo", "foo"}], F). |
|---|
| 104 | |
|---|
| 105 | tree () -> |
|---|
| 106 | Subsub = {directory, "subsubdir", |
|---|
| 107 | [{file, "subsubfile.sub", "titi"}]}, |
|---|
| 108 | Tree = [{file, "foo.txt", "Hello"}, |
|---|
| 109 | {file, "bar.txt", "G'day"}, |
|---|
| 110 | {file, "toto.erl", "-module(toto)."}, |
|---|
| 111 | {directory, "subdir", |
|---|
| 112 | [{file, "subfile.ext", "toto"}, |
|---|
| 113 | Subsub]}], |
|---|
| 114 | Tree. |
|---|
| 115 | |
|---|
| 116 | directories_can_be_removed (Dir, _) -> |
|---|
| 117 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 118 | receive_all (), |
|---|
| 119 | Subdir = filename: join (Dir, "subdir"), |
|---|
| 120 | fixtures: delete_tree (Subdir), |
|---|
| 121 | Watcher ! check, |
|---|
| 122 | {directory, Subdir, lost} = receive_one (), |
|---|
| 123 | ok. |
|---|
| 124 | |
|---|
| 125 | directories_can_be_removed_when_recursive (Dir, _) -> |
|---|
| 126 | Watcher = spawn_link (directory_watcher, init_recursive, [Dir, send_me ()]), |
|---|
| 127 | receive_all (), |
|---|
| 128 | Subdir = filename: join ([Dir, "subdir", "subsubdir"]), |
|---|
| 129 | ok = fixtures: delete_tree (Subdir), |
|---|
| 130 | Watcher ! check, |
|---|
| 131 | File = filename: join (Subdir, "subsubfile.sub"), |
|---|
| 132 | [{{file, ".sub"}, File, lost}, {directory, Subdir, lost}] = receive_all (), |
|---|
| 133 | ok. |
|---|
| 134 | |
|---|
| 135 | recursive (Dir, _) -> |
|---|
| 136 | Watcher = spawn_link (directory_watcher, init_recursive, [Dir, send_me ()]), |
|---|
| 137 | Subdir = filename: join (Dir, "subdir"), |
|---|
| 138 | Found_subdir = {directory, Subdir, found}, |
|---|
| 139 | ok = receive_until_found (Found_subdir), |
|---|
| 140 | Subfile = filename: join (Subdir, "subfile.ext"), |
|---|
| 141 | Found_subfile = {{file, ".ext"}, Subfile, found}, |
|---|
| 142 | ok = receive_until_found (Found_subfile), |
|---|
| 143 | Subsubfile = filename: join ([Subdir, "subsubdir", "subsubfile.sub"]), |
|---|
| 144 | Found_subsub = {{file, ".sub"}, Subsubfile, found}, |
|---|
| 145 | ok = receive_until_found (Found_subsub), |
|---|
| 146 | ok = file: write_file (Subsubfile, "not titi"), |
|---|
| 147 | Watcher ! check, |
|---|
| 148 | Change_subsub = {{file, ".sub"}, Subsubfile, changed}, |
|---|
| 149 | ok = receive_until_found (Change_subsub). |
|---|
| 150 | |
|---|
| 151 | bad_symlinks_are_ignored (Dir, _) -> |
|---|
| 152 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 153 | Link = filename: join (Dir, "titi.erl"), |
|---|
| 154 | Destination = filename: join (Dir, "nofile"), |
|---|
| 155 | case file: make_symlink (Destination, Link) of |
|---|
| 156 | ok -> |
|---|
| 157 | Watcher ! check, |
|---|
| 158 | timeout = receive_one (); |
|---|
| 159 | {error, enotsup} -> |
|---|
| 160 | ok |
|---|
| 161 | end, |
|---|
| 162 | ok. |
|---|
| 163 | |
|---|
| 164 | tests_from_empty (Dir, []) -> |
|---|
| 165 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 166 | timeout = receive_one (), |
|---|
| 167 | |
|---|
| 168 | Filename = filename: join (Dir, "myfile.txt"), |
|---|
| 169 | ok = file: write_file (Filename, list_to_binary ("Hello")), |
|---|
| 170 | Watcher ! check, |
|---|
| 171 | {{file, ".txt"}, Filename, found} = receive_one (), |
|---|
| 172 | |
|---|
| 173 | Subdir = filename: join (Dir, "mydir"), |
|---|
| 174 | ok = file: make_dir (Subdir), |
|---|
| 175 | Watcher ! check, |
|---|
| 176 | {directory, Subdir, found} = receive_one (), |
|---|
| 177 | |
|---|
| 178 | Subfile = filename: join (Subdir, "mysubfile.txt"), |
|---|
| 179 | ok = file: write_file (Subfile, list_to_binary ("Hello")), |
|---|
| 180 | Watcher ! check, |
|---|
| 181 | timeout = receive_one (), |
|---|
| 182 | |
|---|
| 183 | ok = file: delete (Subfile), |
|---|
| 184 | ok = file: del_dir (Subdir), |
|---|
| 185 | Watcher ! check, |
|---|
| 186 | {directory, Subdir, lost} = receive_one (), |
|---|
| 187 | |
|---|
| 188 | rewrite_same_data (Filename), |
|---|
| 189 | Watcher ! check, |
|---|
| 190 | timeout = receive_one (), |
|---|
| 191 | |
|---|
| 192 | ok = file: write_file (Filename, list_to_binary ("Bye")), |
|---|
| 193 | Watcher ! check, |
|---|
| 194 | {{file, ".txt"}, Filename, changed} = receive_one (), |
|---|
| 195 | |
|---|
| 196 | Watcher ! stop, |
|---|
| 197 | bye = receive_one (), |
|---|
| 198 | false = is_process_alive (Watcher), |
|---|
| 199 | ok. |
|---|
| 200 | |
|---|
| 201 | rewrite_same_data (Filename) -> |
|---|
| 202 | ok = file: write_file (Filename, list_to_binary ("Hello")). |
|---|
| 203 | |
|---|
| 204 | tests_with_several (Dir, Tree) -> |
|---|
| 205 | [Foo, Bar, Toto] = [filename: join (Dir, Name) || {file, Name, _} <- Tree], |
|---|
| 206 | Watcher = spawn_link (directory_watcher, init, [Dir, send_me ()]), |
|---|
| 207 | Finds = [receive_one(), receive_one(), receive_one()], |
|---|
| 208 | [FoundBar, FoundFoo, FoundToto] = lists: keysort (2, Finds), |
|---|
| 209 | {{file, ".txt"}, Bar, found} = FoundBar, |
|---|
| 210 | {{file, ".txt"}, Foo, found} = FoundFoo, |
|---|
| 211 | {{file, ".erl"}, Toto, found} = FoundToto, |
|---|
| 212 | |
|---|
| 213 | ok = file: write_file (Foo, list_to_binary ("Bye")), |
|---|
| 214 | Watcher ! check, |
|---|
| 215 | {{file, ".txt"}, Foo, changed} = receive_one (), |
|---|
| 216 | |
|---|
| 217 | ok = file: write_file (Foo, list_to_binary ("Not yet")), |
|---|
| 218 | ok = file: write_file (Bar, list_to_binary ("Bye")), |
|---|
| 219 | Watcher ! check, |
|---|
| 220 | Changes = [receive_one(), receive_one()], |
|---|
| 221 | [ChangedBar, ChangedFoo] = lists: keysort (2, Changes), |
|---|
| 222 | {{file, ".txt"}, Bar, changed} = ChangedBar, |
|---|
| 223 | {{file, ".txt"}, Foo, changed} = ChangedFoo, |
|---|
| 224 | ok. |
|---|
| 225 | |
|---|
| 226 | receive_one () -> |
|---|
| 227 | receive {directory_watcher, _, Event} -> Event |
|---|
| 228 | after 500 -> timeout |
|---|
| 229 | end. |
|---|
| 230 | |
|---|
| 231 | receive_until_found (Event) -> |
|---|
| 232 | receive {directory_watcher, _, Event} -> ok |
|---|
| 233 | after 500 -> timeout |
|---|
| 234 | end. |
|---|
| 235 | |
|---|
| 236 | receive_all () -> |
|---|
| 237 | lists: reverse (receive_all ([])). |
|---|
| 238 | |
|---|
| 239 | receive_all (Acc) -> |
|---|
| 240 | receive {directory_watcher, _, Event} -> |
|---|
| 241 | receive_all ([Event | Acc]) |
|---|
| 242 | after 500 -> |
|---|
| 243 | Acc |
|---|
| 244 | end. |
|---|
| 245 | |
|---|
| 246 | change_from_directory_to_file (Root, [{directory, Name, []}]) -> |
|---|
| 247 | Watcher = spawn_link (directory_watcher, init, [Root, send_me ()]), |
|---|
| 248 | receive_all (), |
|---|
| 249 | Path = filename: join (Root, Name), |
|---|
| 250 | ok = file: del_dir (Path), |
|---|
| 251 | ok = file: write_file (Path, list_to_binary ("hello")), |
|---|
| 252 | Watcher ! check, |
|---|
| 253 | [{directory, Path, lost}, {{file, ""}, Path, found}] = receive_all (), |
|---|
| 254 | ok. |
|---|
| 255 | |
|---|
| 256 | change_from_file_to_directory (Root, [{file, Name, []}]) -> |
|---|
| 257 | Watcher = spawn_link (directory_watcher, init, [Root, send_me ()]), |
|---|
| 258 | receive_all (), |
|---|
| 259 | Path = filename: join (Root, Name), |
|---|
| 260 | ok = file: delete (Path), |
|---|
| 261 | ok = file: make_dir (Path), |
|---|
| 262 | Watcher ! check, |
|---|
| 263 | [{{file, ""}, Path, lost}, {directory, Path, found}] = receive_all (), |
|---|
| 264 | ok. |
|---|
| 265 | |
|---|
| 266 | change_from_file_to_directory_when_recursive (Root, [{file, Name, _}]) -> |
|---|
| 267 | Watcher = spawn_link (directory_watcher, init_recursive, [Root, send_me ()]), |
|---|
| 268 | receive_all (), |
|---|
| 269 | Path = filename: join (Root, Name), |
|---|
| 270 | file: delete (Path), |
|---|
| 271 | ok = file: make_dir (Path), |
|---|
| 272 | Watcher ! check, |
|---|
| 273 | [{{file, ""}, Path, lost}, {directory, Path, found}] = receive_all (), |
|---|
| 274 | New_file = filename: join (Path, "new.txt"), |
|---|
| 275 | ok = file: write_file (New_file, list_to_binary ("new")), |
|---|
| 276 | Watcher ! check, |
|---|
| 277 | [{{file, ".txt"}, New_file, found}] = receive_all (), |
|---|
| 278 | ok. |
|---|
| 279 | |
|---|
| 280 | change_from_directory_to_file_when_recursive (Root, _) -> |
|---|
| 281 | Watcher = spawn_link (directory_watcher, init_recursive, [Root, send_me ()]), |
|---|
| 282 | receive_all (), |
|---|
| 283 | Subdir = filename: join ([Root, "subdir", "subsubdir"]), |
|---|
| 284 | ok = fixtures: delete_tree (Subdir), |
|---|
| 285 | ok = file: write_file (Subdir, list_to_binary ("now a file")), |
|---|
| 286 | Watcher ! check, |
|---|
| 287 | [{{file, ".sub"}, _, lost}, |
|---|
| 288 | {directory, Subdir, lost}, |
|---|
| 289 | {{file, ""}, Subdir, found}] = receive_all (), |
|---|
| 290 | ok. |
|---|
| 291 | |
|---|
| 292 | remove_and_add_in_same_check (Root, _) -> |
|---|
| 293 | Watcher = spawn_link (directory_watcher, init_recursive, [Root, send_me ()]), |
|---|
| 294 | receive_all (), |
|---|
| 295 | [Foo, Bar] = [filename: join (Root, X) || X <- ["foo", "bar"]], |
|---|
| 296 | ok = file: delete (Foo), |
|---|
| 297 | ok = file: write_file (Bar, list_to_binary ("bar")), |
|---|
| 298 | Watcher ! check, |
|---|
| 299 | [{{file, ""}, Bar, found}, |
|---|
| 300 | {{file, ""}, Foo, lost}] = receive_all (), |
|---|
| 301 | ok. |
|---|
| 302 | |
|---|
| 303 | can_replace_all_in_same_check (Root, Tree) -> |
|---|
| 304 | Watcher = spawn_link (directory_watcher, init, [Root, send_me ()]), |
|---|
| 305 | receive_all (), |
|---|
| 306 | Replace = fun ({file, F, _}) -> |
|---|
| 307 | Old = filename: join (Root, F), |
|---|
| 308 | ok = file: delete (Old), |
|---|
| 309 | New = filename: join (Root, F ++ F), |
|---|
| 310 | ok = file: write_file (New, "hello") |
|---|
| 311 | end, |
|---|
| 312 | ok = lists: foreach (Replace, Tree), |
|---|
| 313 | Watcher ! check, |
|---|
| 314 | Lost_files = [filename: join (Root, X) || {file, X, _} <- Tree], |
|---|
| 315 | Found_files = [filename: join (Root, X++X) || {file, X, _} <- Tree], |
|---|
| 316 | Lost = [{{file, ""}, X, lost} || X <- Lost_files], |
|---|
| 317 | Found = [{{file, ""}, X, found} || X <- Found_files], |
|---|
| 318 | Expected = lists: sort (Lost ++ Found), |
|---|
| 319 | Received = lists: sort (receive_all ()), |
|---|
| 320 | {Expected, Expected} = {Expected, Received}, |
|---|
| 321 | ok. |
|---|
| 322 | |
|---|
| 323 | can_find_two_at_a_time (Root, _) -> |
|---|
| 324 | Watcher = spawn_link (directory_watcher, init, [Root, send_me ()]), |
|---|
| 325 | receive_all (), |
|---|
| 326 | [Bar, Baz] = [filename: join (Root, X) || X <- ["bar", "baz"]], |
|---|
| 327 | ok = file: write_file (Bar, "bar"), |
|---|
| 328 | ok = file: write_file (Baz, "baz"), |
|---|
| 329 | Watcher ! check, |
|---|
| 330 | Found = [{{file, ""}, X, found} || X <- [Bar, Baz]], |
|---|
| 331 | Expected = lists: sort (Found), |
|---|
| 332 | Received = lists: sort (receive_all ()), |
|---|
| 333 | {Expected, Expected} = {Expected, Received}, |
|---|
| 334 | ok. |
|---|
| 335 | |
|---|
| 336 | send_me () -> |
|---|
| 337 | Self = self (), |
|---|
| 338 | fun (E) -> |
|---|
| 339 | Self ! {directory_watcher, self (), E} |
|---|
| 340 | end. |
|---|