1Code.require_file("test_helper.exs", __DIR__) 2 3defmodule RegistryTest do 4 use ExUnit.Case, async: true 5 doctest Registry, except: [:moduledoc] 6 7 setup config do 8 keys = config[:keys] || :unique 9 partitions = config[:partitions] || 1 10 listeners = List.wrap(config[:listener]) 11 opts = [keys: keys, name: config.test, partitions: partitions, listeners: listeners] 12 {:ok, _} = start_supervised({Registry, opts}) 13 {:ok, %{registry: config.test, partitions: partitions}} 14 end 15 16 for {describe, partitions} <- ["with 1 partition": 1, "with 8 partitions": 8] do 17 describe "unique #{describe}" do 18 @describetag keys: :unique, partitions: partitions 19 20 test "starts configured number of partitions", %{registry: registry, partitions: partitions} do 21 assert length(Supervisor.which_children(registry)) == partitions 22 end 23 24 test "counts 0 keys in an empty registry", %{registry: registry} do 25 assert 0 == Registry.count(registry) 26 end 27 28 test "counts the number of keys in a registry", %{registry: registry} do 29 {:ok, _} = Registry.register(registry, "hello", :value) 30 {:ok, _} = Registry.register(registry, "world", :value) 31 32 assert 2 == Registry.count(registry) 33 end 34 35 test "has unique registrations", %{registry: registry} do 36 {:ok, pid} = Registry.register(registry, "hello", :value) 37 assert is_pid(pid) 38 assert Registry.keys(registry, self()) == ["hello"] 39 assert Registry.values(registry, "hello", self()) == [:value] 40 41 assert {:error, {:already_registered, pid}} = Registry.register(registry, "hello", :value) 42 assert pid == self() 43 assert Registry.keys(registry, self()) == ["hello"] 44 assert Registry.values(registry, "hello", self()) == [:value] 45 46 {:ok, pid} = Registry.register(registry, "world", :value) 47 assert is_pid(pid) 48 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello", "world"] 49 end 50 51 test "has unique registrations across processes", %{registry: registry} do 52 {_, task} = register_task(registry, "hello", :value) 53 Process.link(Process.whereis(registry)) 54 assert Registry.keys(registry, task) == ["hello"] 55 assert Registry.values(registry, "hello", task) == [:value] 56 57 assert {:error, {:already_registered, ^task}} = 58 Registry.register(registry, "hello", :recent) 59 60 assert Registry.keys(registry, self()) == [] 61 assert Registry.values(registry, "hello", self()) == [] 62 63 {:links, links} = Process.info(self(), :links) 64 assert Process.whereis(registry) in links 65 end 66 67 test "has unique registrations even if partition is delayed", %{registry: registry} do 68 {owner, task} = register_task(registry, "hello", :value) 69 70 assert Registry.register(registry, "hello", :other) == 71 {:error, {:already_registered, task}} 72 73 :sys.suspend(owner) 74 kill_and_assert_down(task) 75 Registry.register(registry, "hello", :other) 76 assert Registry.lookup(registry, "hello") == [{self(), :other}] 77 end 78 79 test "supports match patterns", %{registry: registry} do 80 value = {1, :atom, 1} 81 {:ok, _} = Registry.register(registry, "hello", value) 82 assert Registry.match(registry, "hello", {1, :_, :_}) == [{self(), value}] 83 assert Registry.match(registry, "hello", {1.0, :_, :_}) == [] 84 assert Registry.match(registry, "hello", {:_, :atom, :_}) == [{self(), value}] 85 assert Registry.match(registry, "hello", {:"$1", :_, :"$1"}) == [{self(), value}] 86 assert Registry.match(registry, "hello", :_) == [{self(), value}] 87 assert Registry.match(registry, :_, :_) == [] 88 89 value2 = %{a: "a", b: "b"} 90 {:ok, _} = Registry.register(registry, "world", value2) 91 assert Registry.match(registry, "world", %{b: "b"}) == [{self(), value2}] 92 end 93 94 test "supports guard conditions", %{registry: registry} do 95 value = {1, :atom, 2} 96 {:ok, _} = Registry.register(registry, "hello", value) 97 98 assert Registry.match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}]) == 99 [{self(), value}] 100 101 assert Registry.match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 2}]) == [] 102 103 assert Registry.match(registry, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}]) == 104 [{self(), value}] 105 end 106 107 test "count_match supports match patterns", %{registry: registry} do 108 value = {1, :atom, 1} 109 {:ok, _} = Registry.register(registry, "hello", value) 110 assert 1 == Registry.count_match(registry, "hello", {1, :_, :_}) 111 assert 0 == Registry.count_match(registry, "hello", {1.0, :_, :_}) 112 assert 1 == Registry.count_match(registry, "hello", {:_, :atom, :_}) 113 assert 1 == Registry.count_match(registry, "hello", {:"$1", :_, :"$1"}) 114 assert 1 == Registry.count_match(registry, "hello", :_) 115 assert 0 == Registry.count_match(registry, :_, :_) 116 117 value2 = %{a: "a", b: "b"} 118 {:ok, _} = Registry.register(registry, "world", value2) 119 assert 1 == Registry.count_match(registry, "world", %{b: "b"}) 120 end 121 122 test "count_match supports guard conditions", %{registry: registry} do 123 value = {1, :atom, 2} 124 {:ok, _} = Registry.register(registry, "hello", value) 125 126 assert 1 == Registry.count_match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}]) 127 assert 0 == Registry.count_match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 2}]) 128 assert 1 == Registry.count_match(registry, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}]) 129 end 130 131 test "unregister_match supports patterns", %{registry: registry} do 132 value = {1, :atom, 1} 133 {:ok, _} = Registry.register(registry, "hello", value) 134 135 Registry.unregister_match(registry, "hello", {2, :_, :_}) 136 assert Registry.lookup(registry, "hello") == [{self(), value}] 137 Registry.unregister_match(registry, "hello", {1.0, :_, :_}) 138 assert Registry.lookup(registry, "hello") == [{self(), value}] 139 Registry.unregister_match(registry, "hello", {:_, :atom, :_}) 140 assert Registry.lookup(registry, "hello") == [] 141 end 142 143 test "unregister_match supports guards", %{registry: registry} do 144 value = {1, :atom, 1} 145 {:ok, _} = Registry.register(registry, "hello", value) 146 147 Registry.unregister_match(registry, "hello", {:"$1", :_, :_}, [{:<, :"$1", 2}]) 148 assert Registry.lookup(registry, "hello") == [] 149 end 150 151 test "unregister_match supports tricky keys", %{registry: registry} do 152 {:ok, _} = Registry.register(registry, :_, :foo) 153 {:ok, _} = Registry.register(registry, "hello", "b") 154 155 Registry.unregister_match(registry, :_, :foo) 156 assert Registry.lookup(registry, :_) == [] 157 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello"] 158 end 159 160 test "compares using ===", %{registry: registry} do 161 {:ok, _} = Registry.register(registry, 1.0, :value) 162 {:ok, _} = Registry.register(registry, 1, :value) 163 assert Registry.keys(registry, self()) |> Enum.sort() == [1, 1.0] 164 end 165 166 test "updates current process value", %{registry: registry} do 167 assert Registry.update_value(registry, "hello", &raise/1) == :error 168 register_task(registry, "hello", :value) 169 assert Registry.update_value(registry, "hello", &raise/1) == :error 170 171 Registry.register(registry, "world", 1) 172 assert Registry.lookup(registry, "world") == [{self(), 1}] 173 assert Registry.update_value(registry, "world", &(&1 + 1)) == {2, 1} 174 assert Registry.lookup(registry, "world") == [{self(), 2}] 175 end 176 177 test "dispatches to a single key", %{registry: registry} do 178 fun = fn _ -> raise "will never be invoked" end 179 assert Registry.dispatch(registry, "hello", fun) == :ok 180 181 {:ok, _} = Registry.register(registry, "hello", :value) 182 183 fun = fn [{pid, value}] -> send(pid, {:dispatch, value}) end 184 assert Registry.dispatch(registry, "hello", fun) 185 186 assert_received {:dispatch, :value} 187 end 188 189 test "unregisters process by key", %{registry: registry} do 190 :ok = Registry.unregister(registry, "hello") 191 192 {:ok, _} = Registry.register(registry, "hello", :value) 193 {:ok, _} = Registry.register(registry, "world", :value) 194 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello", "world"] 195 196 :ok = Registry.unregister(registry, "hello") 197 assert Registry.keys(registry, self()) == ["world"] 198 199 :ok = Registry.unregister(registry, "world") 200 assert Registry.keys(registry, self()) == [] 201 end 202 203 test "unregisters with no entries", %{registry: registry} do 204 assert Registry.unregister(registry, "hello") == :ok 205 end 206 207 test "unregisters with tricky keys", %{registry: registry} do 208 {:ok, _} = Registry.register(registry, :_, :foo) 209 {:ok, _} = Registry.register(registry, "hello", "b") 210 211 Registry.unregister(registry, :_) 212 assert Registry.lookup(registry, :_) == [] 213 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello"] 214 end 215 216 @tag listener: :"unique_listener_#{partitions}" 217 test "allows listeners", %{registry: registry, listener: listener} do 218 Process.register(self(), listener) 219 {_, task} = register_task(registry, "hello", :world) 220 assert_received {:register, ^registry, "hello", ^task, :world} 221 222 self = self() 223 {:ok, _} = Registry.register(registry, "world", :value) 224 assert_received {:register, ^registry, "world", ^self, :value} 225 226 :ok = Registry.unregister(registry, "world") 227 assert_received {:unregister, ^registry, "world", ^self} 228 end 229 230 test "links and unlinks on register/unregister", %{registry: registry} do 231 {:ok, pid} = Registry.register(registry, "hello", :value) 232 {:links, links} = Process.info(self(), :links) 233 assert pid in links 234 235 {:ok, pid} = Registry.register(registry, "world", :value) 236 {:links, links} = Process.info(self(), :links) 237 assert pid in links 238 239 :ok = Registry.unregister(registry, "hello") 240 {:links, links} = Process.info(self(), :links) 241 assert pid in links 242 243 :ok = Registry.unregister(registry, "world") 244 {:links, links} = Process.info(self(), :links) 245 refute pid in links 246 end 247 248 test "raises on unknown registry name" do 249 assert_raise ArgumentError, ~r/unknown registry/, fn -> 250 Registry.register(:unknown, "hello", :value) 251 end 252 end 253 254 test "via callbacks", %{registry: registry} do 255 name = {:via, Registry, {registry, "hello"}} 256 257 # register_name 258 {:ok, pid} = Agent.start_link(fn -> 0 end, name: name) 259 260 # send 261 assert Agent.update(name, &(&1 + 1)) == :ok 262 263 # whereis_name 264 assert Agent.get(name, & &1) == 1 265 266 # unregister_name 267 assert {:error, _} = Agent.start(fn -> raise "oops" end) 268 269 # errors 270 assert {:error, {:already_started, ^pid}} = Agent.start(fn -> 0 end, name: name) 271 end 272 273 test "uses value provided in via", %{registry: registry} do 274 name = {:via, Registry, {registry, "hello", :value}} 275 {:ok, pid} = Agent.start_link(fn -> 0 end, name: name) 276 assert Registry.lookup(registry, "hello") == [{pid, :value}] 277 end 278 279 test "empty list for empty registry", %{registry: registry} do 280 assert Registry.select(registry, [{{:_, :_, :_}, [], [:"$_"]}]) == [] 281 end 282 283 test "select all", %{registry: registry} do 284 name = {:via, Registry, {registry, "hello"}} 285 {:ok, pid} = Agent.start_link(fn -> 0 end, name: name) 286 {:ok, _} = Registry.register(registry, "world", :value) 287 288 assert Registry.select(registry, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}]) 289 |> Enum.sort() == 290 [{"hello", pid, nil}, {"world", self(), :value}] 291 end 292 293 test "select supports full match specs", %{registry: registry} do 294 value = {1, :atom, 1} 295 {:ok, _} = Registry.register(registry, "hello", value) 296 297 assert [{"hello", self(), value}] == 298 Registry.select(registry, [ 299 {{"hello", :"$2", :"$3"}, [], [{{"hello", :"$2", :"$3"}}]} 300 ]) 301 302 assert [{"hello", self(), value}] == 303 Registry.select(registry, [ 304 {{:"$1", self(), :"$3"}, [], [{{:"$1", self(), :"$3"}}]} 305 ]) 306 307 assert [{"hello", self(), value}] == 308 Registry.select(registry, [ 309 {{:"$1", :"$2", value}, [], [{{:"$1", :"$2", {value}}}]} 310 ]) 311 312 assert [] == 313 Registry.select(registry, [ 314 {{"world", :"$2", :"$3"}, [], [{{"world", :"$2", :"$3"}}]} 315 ]) 316 317 assert [] == Registry.select(registry, [{{:"$1", :"$2", {1.0, :_, :_}}, [], [:"$_"]}]) 318 319 assert [{"hello", self(), value}] == 320 Registry.select(registry, [ 321 {{:"$1", :"$2", {:"$3", :atom, :"$4"}}, [], 322 [{{:"$1", :"$2", {{:"$3", :atom, :"$4"}}}}]} 323 ]) 324 325 assert [{"hello", self(), {1, :atom, 1}}] == 326 Registry.select(registry, [ 327 {{:"$1", :"$2", {:"$3", :"$4", :"$3"}}, [], 328 [{{:"$1", :"$2", {{:"$3", :"$4", :"$3"}}}}]} 329 ]) 330 331 value2 = %{a: "a", b: "b"} 332 {:ok, _} = Registry.register(registry, "world", value2) 333 334 assert [:match] == 335 Registry.select(registry, [{{"world", self(), %{b: "b"}}, [], [:match]}]) 336 337 assert ["hello", "world"] == 338 Registry.select(registry, [{{:"$1", :_, :_}, [], [:"$1"]}]) |> Enum.sort() 339 end 340 341 test "select supports guard conditions", %{registry: registry} do 342 value = {1, :atom, 2} 343 {:ok, _} = Registry.register(registry, "hello", value) 344 345 assert [{"hello", self(), {1, :atom, 2}}] == 346 Registry.select(registry, [ 347 {{:"$1", :"$2", {:"$3", :"$4", :"$5"}}, [{:>, :"$5", 1}], 348 [{{:"$1", :"$2", {{:"$3", :"$4", :"$5"}}}}]} 349 ]) 350 351 assert [] == 352 Registry.select(registry, [ 353 {{:_, :_, {:_, :_, :"$1"}}, [{:>, :"$1", 2}], [:"$_"]} 354 ]) 355 356 assert ["hello"] == 357 Registry.select(registry, [ 358 {{:"$1", :_, {:_, :"$2", :_}}, [{:is_atom, :"$2"}], [:"$1"]} 359 ]) 360 end 361 362 test "select allows multiple specs", %{registry: registry} do 363 {:ok, _} = Registry.register(registry, "hello", :value) 364 {:ok, _} = Registry.register(registry, "world", :value) 365 366 assert ["hello", "world"] == 367 Registry.select(registry, [ 368 {{"hello", :_, :_}, [], [{:element, 1, :"$_"}]}, 369 {{"world", :_, :_}, [], [{:element, 1, :"$_"}]} 370 ]) 371 |> Enum.sort() 372 end 373 374 test "raises on incorrect shape of match spec", %{registry: registry} do 375 assert_raise ArgumentError, fn -> 376 Registry.select(registry, [{:_, [], []}]) 377 end 378 end 379 380 test "doesn't grow ets on already_registered", 381 %{registry: registry, partitions: partitions} do 382 assert sum_pid_entries(registry, partitions) == 0 383 384 {:ok, pid} = Registry.register(registry, "hello", :value) 385 assert is_pid(pid) 386 assert sum_pid_entries(registry, partitions) == 1 387 388 {:ok, pid} = Registry.register(registry, "world", :value) 389 assert is_pid(pid) 390 assert sum_pid_entries(registry, partitions) == 2 391 392 assert {:error, {:already_registered, _pid}} = 393 Registry.register(registry, "hello", :value) 394 395 assert sum_pid_entries(registry, partitions) == 2 396 end 397 398 test "doesn't grow ets on already_registered across processes", 399 %{registry: registry, partitions: partitions} do 400 assert sum_pid_entries(registry, partitions) == 0 401 402 {_, task} = register_task(registry, "hello", :value) 403 Process.link(Process.whereis(registry)) 404 405 assert sum_pid_entries(registry, partitions) == 1 406 407 {:ok, pid} = Registry.register(registry, "world", :value) 408 assert is_pid(pid) 409 assert sum_pid_entries(registry, partitions) == 2 410 411 assert {:error, {:already_registered, ^task}} = 412 Registry.register(registry, "hello", :recent) 413 414 assert sum_pid_entries(registry, partitions) == 2 415 end 416 end 417 end 418 419 for {describe, partitions} <- ["with 1 partition": 1, "with 8 partitions": 8] do 420 describe "duplicate #{describe}" do 421 @describetag keys: :duplicate, partitions: partitions 422 423 test "starts configured number of partitions", %{registry: registry, partitions: partitions} do 424 assert length(Supervisor.which_children(registry)) == partitions 425 end 426 427 test "counts 0 keys in an empty registry", %{registry: registry} do 428 assert 0 == Registry.count(registry) 429 end 430 431 test "counts the number of keys in a registry", %{registry: registry} do 432 {:ok, _} = Registry.register(registry, "hello", :value) 433 {:ok, _} = Registry.register(registry, "hello", :value) 434 435 assert 2 == Registry.count(registry) 436 end 437 438 test "has duplicate registrations", %{registry: registry} do 439 {:ok, pid} = Registry.register(registry, "hello", :value) 440 assert is_pid(pid) 441 assert Registry.keys(registry, self()) == ["hello"] 442 assert Registry.values(registry, "hello", self()) == [:value] 443 444 assert {:ok, pid} = Registry.register(registry, "hello", :value) 445 assert is_pid(pid) 446 assert Registry.keys(registry, self()) == ["hello", "hello"] 447 assert Registry.values(registry, "hello", self()) == [:value, :value] 448 449 {:ok, pid} = Registry.register(registry, "world", :value) 450 assert is_pid(pid) 451 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello", "hello", "world"] 452 end 453 454 test "has duplicate registrations across processes", %{registry: registry} do 455 {_, task} = register_task(registry, "hello", :world) 456 assert Registry.keys(registry, self()) == [] 457 assert Registry.keys(registry, task) == ["hello"] 458 assert Registry.values(registry, "hello", self()) == [] 459 assert Registry.values(registry, "hello", task) == [:world] 460 461 assert {:ok, _pid} = Registry.register(registry, "hello", :value) 462 assert Registry.keys(registry, self()) == ["hello"] 463 assert Registry.values(registry, "hello", self()) == [:value] 464 end 465 466 test "compares using matches", %{registry: registry} do 467 {:ok, _} = Registry.register(registry, 1.0, :value) 468 {:ok, _} = Registry.register(registry, 1, :value) 469 assert Registry.keys(registry, self()) |> Enum.sort() == [1, 1.0] 470 end 471 472 test "dispatches to multiple keys in serial", %{registry: registry} do 473 Process.flag(:trap_exit, true) 474 parent = self() 475 476 fun = fn _ -> raise "will never be invoked" end 477 assert Registry.dispatch(registry, "hello", fun, parallel: false) == :ok 478 479 {:ok, _} = Registry.register(registry, "hello", :value1) 480 {:ok, _} = Registry.register(registry, "hello", :value2) 481 {:ok, _} = Registry.register(registry, "world", :value3) 482 483 fun = fn entries -> 484 assert parent == self() 485 for {pid, value} <- entries, do: send(pid, {:dispatch, value}) 486 end 487 488 assert Registry.dispatch(registry, "hello", fun, parallel: false) 489 490 assert_received {:dispatch, :value1} 491 assert_received {:dispatch, :value2} 492 refute_received {:dispatch, :value3} 493 494 fun = fn entries -> 495 assert parent == self() 496 for {pid, value} <- entries, do: send(pid, {:dispatch, value}) 497 end 498 499 assert Registry.dispatch(registry, "world", fun, parallel: false) 500 501 refute_received {:dispatch, :value1} 502 refute_received {:dispatch, :value2} 503 assert_received {:dispatch, :value3} 504 505 refute_received {:EXIT, _, _} 506 end 507 508 test "dispatches to multiple keys in parallel", context do 509 %{registry: registry, partitions: partitions} = context 510 Process.flag(:trap_exit, true) 511 parent = self() 512 513 fun = fn _ -> raise "will never be invoked" end 514 assert Registry.dispatch(registry, "hello", fun, parallel: true) == :ok 515 516 {:ok, _} = Registry.register(registry, "hello", :value1) 517 {:ok, _} = Registry.register(registry, "hello", :value2) 518 {:ok, _} = Registry.register(registry, "world", :value3) 519 520 fun = fn entries -> 521 if partitions == 8 do 522 assert parent != self() 523 else 524 assert parent == self() 525 end 526 527 for {pid, value} <- entries, do: send(pid, {:dispatch, value}) 528 end 529 530 assert Registry.dispatch(registry, "hello", fun, parallel: true) 531 532 assert_received {:dispatch, :value1} 533 assert_received {:dispatch, :value2} 534 refute_received {:dispatch, :value3} 535 536 fun = fn entries -> 537 if partitions == 8 do 538 assert parent != self() 539 else 540 assert parent == self() 541 end 542 543 for {pid, value} <- entries, do: send(pid, {:dispatch, value}) 544 end 545 546 assert Registry.dispatch(registry, "world", fun, parallel: true) 547 548 refute_received {:dispatch, :value1} 549 refute_received {:dispatch, :value2} 550 assert_received {:dispatch, :value3} 551 552 refute_received {:EXIT, _, _} 553 end 554 555 test "unregisters by key", %{registry: registry} do 556 {:ok, _} = Registry.register(registry, "hello", :value) 557 {:ok, _} = Registry.register(registry, "hello", :value) 558 {:ok, _} = Registry.register(registry, "world", :value) 559 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello", "hello", "world"] 560 561 :ok = Registry.unregister(registry, "hello") 562 assert Registry.keys(registry, self()) == ["world"] 563 564 :ok = Registry.unregister(registry, "world") 565 assert Registry.keys(registry, self()) == [] 566 end 567 568 test "unregisters with no entries", %{registry: registry} do 569 assert Registry.unregister(registry, "hello") == :ok 570 end 571 572 test "unregisters with tricky keys", %{registry: registry} do 573 {:ok, _} = Registry.register(registry, :_, :foo) 574 {:ok, _} = Registry.register(registry, :_, :bar) 575 {:ok, _} = Registry.register(registry, "hello", "a") 576 {:ok, _} = Registry.register(registry, "hello", "b") 577 578 Registry.unregister(registry, :_) 579 assert Registry.keys(registry, self()) |> Enum.sort() == ["hello", "hello"] 580 end 581 582 test "supports match patterns", %{registry: registry} do 583 value1 = {1, :atom, 1} 584 value2 = {2, :atom, 2} 585 586 {:ok, _} = Registry.register(registry, "hello", value1) 587 {:ok, _} = Registry.register(registry, "hello", value2) 588 589 assert Registry.match(registry, "hello", {1, :_, :_}) == [{self(), value1}] 590 assert Registry.match(registry, "hello", {1.0, :_, :_}) == [] 591 592 assert Registry.match(registry, "hello", {:_, :atom, :_}) |> Enum.sort() == 593 [{self(), value1}, {self(), value2}] 594 595 assert Registry.match(registry, "hello", {:"$1", :_, :"$1"}) |> Enum.sort() == 596 [{self(), value1}, {self(), value2}] 597 598 assert Registry.match(registry, "hello", {2, :_, :_}) == [{self(), value2}] 599 assert Registry.match(registry, "hello", {2.0, :_, :_}) == [] 600 end 601 602 test "supports guards", %{registry: registry} do 603 value1 = {1, :atom, 1} 604 value2 = {2, :atom, 2} 605 606 {:ok, _} = Registry.register(registry, "hello", value1) 607 {:ok, _} = Registry.register(registry, "hello", value2) 608 609 assert Registry.match(registry, "hello", {:"$1", :_, :_}, [{:<, :"$1", 2}]) == 610 [{self(), value1}] 611 612 assert Registry.match(registry, "hello", {:"$1", :_, :_}, [{:>, :"$1", 3}]) == [] 613 614 assert Registry.match(registry, "hello", {:"$1", :_, :_}, [{:<, :"$1", 3}]) |> Enum.sort() == 615 [{self(), value1}, {self(), value2}] 616 617 assert Registry.match(registry, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}]) 618 |> Enum.sort() == [{self(), value1}, {self(), value2}] 619 end 620 621 test "count_match supports match patterns", %{registry: registry} do 622 value = {1, :atom, 1} 623 {:ok, _} = Registry.register(registry, "hello", value) 624 assert 1 == Registry.count_match(registry, "hello", {1, :_, :_}) 625 assert 0 == Registry.count_match(registry, "hello", {1.0, :_, :_}) 626 assert 1 == Registry.count_match(registry, "hello", {:_, :atom, :_}) 627 assert 1 == Registry.count_match(registry, "hello", {:"$1", :_, :"$1"}) 628 assert 1 == Registry.count_match(registry, "hello", :_) 629 assert 0 == Registry.count_match(registry, :_, :_) 630 631 value2 = %{a: "a", b: "b"} 632 {:ok, _} = Registry.register(registry, "world", value2) 633 assert 1 == Registry.count_match(registry, "world", %{b: "b"}) 634 end 635 636 test "count_match supports guard conditions", %{registry: registry} do 637 value = {1, :atom, 2} 638 {:ok, _} = Registry.register(registry, "hello", value) 639 640 assert 1 == Registry.count_match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}]) 641 assert 0 == Registry.count_match(registry, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 2}]) 642 assert 1 == Registry.count_match(registry, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}]) 643 end 644 645 test "unregister_match supports patterns", %{registry: registry} do 646 value1 = {1, :atom, 1} 647 value2 = {2, :atom, 2} 648 649 {:ok, _} = Registry.register(registry, "hello", value1) 650 {:ok, _} = Registry.register(registry, "hello", value2) 651 652 Registry.unregister_match(registry, "hello", {2, :_, :_}) 653 assert Registry.lookup(registry, "hello") == [{self(), value1}] 654 655 {:ok, _} = Registry.register(registry, "hello", value2) 656 Registry.unregister_match(registry, "hello", {2.0, :_, :_}) 657 assert Registry.lookup(registry, "hello") == [{self(), value1}, {self(), value2}] 658 Registry.unregister_match(registry, "hello", {:_, :atom, :_}) 659 assert Registry.lookup(registry, "hello") == [] 660 end 661 662 test "unregister_match supports guards", %{registry: registry} do 663 value1 = {1, :atom, 1} 664 value2 = {2, :atom, 2} 665 666 {:ok, _} = Registry.register(registry, "hello", value1) 667 {:ok, _} = Registry.register(registry, "hello", value2) 668 669 Registry.unregister_match(registry, "hello", {:"$1", :_, :_}, [{:<, :"$1", 2}]) 670 assert Registry.lookup(registry, "hello") == [{self(), value2}] 671 end 672 673 test "unregister_match supports tricky keys", %{registry: registry} do 674 {:ok, _} = Registry.register(registry, :_, :foo) 675 {:ok, _} = Registry.register(registry, :_, :bar) 676 {:ok, _} = Registry.register(registry, "hello", "a") 677 {:ok, _} = Registry.register(registry, "hello", "b") 678 679 Registry.unregister_match(registry, :_, :foo) 680 assert Registry.lookup(registry, :_) == [{self(), :bar}] 681 682 assert Registry.keys(registry, self()) |> Enum.sort() == [:_, "hello", "hello"] 683 end 684 685 @tag listener: :"duplicate_listener_#{partitions}" 686 test "allows listeners", %{registry: registry, listener: listener} do 687 Process.register(self(), listener) 688 {_, task} = register_task(registry, "hello", :world) 689 assert_received {:register, ^registry, "hello", ^task, :world} 690 691 self = self() 692 {:ok, _} = Registry.register(registry, "hello", :value) 693 assert_received {:register, ^registry, "hello", ^self, :value} 694 695 :ok = Registry.unregister(registry, "hello") 696 assert_received {:unregister, ^registry, "hello", ^self} 697 end 698 699 test "links and unlinks on register/unregister", %{registry: registry} do 700 {:ok, pid} = Registry.register(registry, "hello", :value) 701 {:links, links} = Process.info(self(), :links) 702 assert pid in links 703 704 {:ok, pid} = Registry.register(registry, "world", :value) 705 {:links, links} = Process.info(self(), :links) 706 assert pid in links 707 708 :ok = Registry.unregister(registry, "hello") 709 {:links, links} = Process.info(self(), :links) 710 assert pid in links 711 712 :ok = Registry.unregister(registry, "world") 713 {:links, links} = Process.info(self(), :links) 714 refute pid in links 715 end 716 717 test "raises on unknown registry name" do 718 assert_raise ArgumentError, ~r/unknown registry/, fn -> 719 Registry.register(:unknown, "hello", :value) 720 end 721 end 722 723 test "raises if attempt to be used on via", %{registry: registry} do 724 assert_raise ArgumentError, ":via is not supported for duplicate registries", fn -> 725 name = {:via, Registry, {registry, "hello"}} 726 Agent.start_link(fn -> 0 end, name: name) 727 end 728 end 729 730 test "empty list for empty registry", %{registry: registry} do 731 assert Registry.select(registry, [{{:_, :_, :_}, [], [:"$_"]}]) == [] 732 end 733 734 test "select all", %{registry: registry} do 735 {:ok, _} = Registry.register(registry, "hello", :value) 736 {:ok, _} = Registry.register(registry, "hello", :value) 737 738 assert Registry.select(registry, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}]) 739 |> Enum.sort() == 740 [{"hello", self(), :value}, {"hello", self(), :value}] 741 end 742 743 test "select supports full match specs", %{registry: registry} do 744 value = {1, :atom, 1} 745 {:ok, _} = Registry.register(registry, "hello", value) 746 747 assert [{"hello", self(), value}] == 748 Registry.select(registry, [ 749 {{"hello", :"$2", :"$3"}, [], [{{"hello", :"$2", :"$3"}}]} 750 ]) 751 752 assert [{"hello", self(), value}] == 753 Registry.select(registry, [ 754 {{:"$1", self(), :"$3"}, [], [{{:"$1", self(), :"$3"}}]} 755 ]) 756 757 assert [{"hello", self(), value}] == 758 Registry.select(registry, [ 759 {{:"$1", :"$2", value}, [], [{{:"$1", :"$2", {value}}}]} 760 ]) 761 762 assert [] == 763 Registry.select(registry, [ 764 {{"world", :"$2", :"$3"}, [], [{{"world", :"$2", :"$3"}}]} 765 ]) 766 767 assert [] == Registry.select(registry, [{{:"$1", :"$2", {1.0, :_, :_}}, [], [:"$_"]}]) 768 769 assert [{"hello", self(), value}] == 770 Registry.select(registry, [ 771 {{:"$1", :"$2", {:"$3", :atom, :"$4"}}, [], 772 [{{:"$1", :"$2", {{:"$3", :atom, :"$4"}}}}]} 773 ]) 774 775 assert [{"hello", self(), {1, :atom, 1}}] == 776 Registry.select(registry, [ 777 {{:"$1", :"$2", {:"$3", :"$4", :"$3"}}, [], 778 [{{:"$1", :"$2", {{:"$3", :"$4", :"$3"}}}}]} 779 ]) 780 781 value2 = %{a: "a", b: "b"} 782 {:ok, _} = Registry.register(registry, "world", value2) 783 784 assert [:match] == 785 Registry.select(registry, [{{"world", self(), %{b: "b"}}, [], [:match]}]) 786 787 assert ["hello", "world"] == 788 Registry.select(registry, [{{:"$1", :_, :_}, [], [:"$1"]}]) |> Enum.sort() 789 end 790 791 test "select supports guard conditions", %{registry: registry} do 792 value = {1, :atom, 2} 793 {:ok, _} = Registry.register(registry, "hello", value) 794 795 assert [{"hello", self(), {1, :atom, 2}}] == 796 Registry.select(registry, [ 797 {{:"$1", :"$2", {:"$3", :"$4", :"$5"}}, [{:>, :"$5", 1}], 798 [{{:"$1", :"$2", {{:"$3", :"$4", :"$5"}}}}]} 799 ]) 800 801 assert [] == 802 Registry.select(registry, [ 803 {{:_, :_, {:_, :_, :"$1"}}, [{:>, :"$1", 2}], [:"$_"]} 804 ]) 805 806 assert ["hello"] == 807 Registry.select(registry, [ 808 {{:"$1", :_, {:_, :"$2", :_}}, [{:is_atom, :"$2"}], [:"$1"]} 809 ]) 810 end 811 812 test "select allows multiple specs", %{registry: registry} do 813 {:ok, _} = Registry.register(registry, "hello", :value) 814 {:ok, _} = Registry.register(registry, "world", :value) 815 816 assert ["hello", "world"] == 817 Registry.select(registry, [ 818 {{"hello", :_, :_}, [], [{:element, 1, :"$_"}]}, 819 {{"world", :_, :_}, [], [{:element, 1, :"$_"}]} 820 ]) 821 |> Enum.sort() 822 end 823 end 824 end 825 826 # Note: those tests relies on internals 827 for keys <- [:unique, :duplicate] do 828 describe "clean up #{keys} registry on process crash" do 829 @describetag keys: keys 830 831 @tag partitions: 8 832 test "with 8 partitions", %{registry: registry} do 833 {_, task1} = register_task(registry, "hello", :value) 834 {_, task2} = register_task(registry, "world", :value) 835 836 kill_and_assert_down(task1) 837 kill_and_assert_down(task2) 838 839 # pid might be in different partition to key so need to sync with all 840 # partitions before checking ETS tables are empty. 841 for i <- 0..7 do 842 [{_, _, {partition, _}}] = :ets.lookup(registry, i) 843 GenServer.call(partition, :sync) 844 end 845 846 for i <- 0..7 do 847 [{_, key, {_, pid}}] = :ets.lookup(registry, i) 848 assert :ets.tab2list(key) == [] 849 assert :ets.tab2list(pid) == [] 850 end 851 end 852 853 @tag partitions: 1 854 test "with 1 partition", %{registry: registry} do 855 {_, task1} = register_task(registry, "hello", :value) 856 {_, task2} = register_task(registry, "world", :value) 857 858 kill_and_assert_down(task1) 859 kill_and_assert_down(task2) 860 861 [{-1, {_, _, key, {partition, pid}, _}}] = :ets.lookup(registry, -1) 862 GenServer.call(partition, :sync) 863 assert :ets.tab2list(key) == [] 864 assert :ets.tab2list(pid) == [] 865 end 866 end 867 end 868 869 test "child_spec/1 uses :name as :id" do 870 assert %{id: :custom_name} = Registry.child_spec(name: :custom_name) 871 assert %{id: Registry} = Registry.child_spec([]) 872 end 873 874 test "raises if :name is missing" do 875 assert_raise ArgumentError, ~r/expected :name option to be present/, fn -> 876 Registry.start_link(keys: :unique) 877 end 878 end 879 880 test "raises if :name is not an atom" do 881 assert_raise ArgumentError, ~r/expected :name to be an atom, got/, fn -> 882 Registry.start_link(keys: :unique, name: []) 883 end 884 end 885 886 test "raises if :compressed is not a boolean" do 887 assert_raise ArgumentError, ~r/expected :compressed to be a boolean, got/, fn -> 888 Registry.start_link(keys: :unique, name: :name, compressed: :fail) 889 end 890 end 891 892 test "unregistration on crash with {registry, key, value} via tuple", %{registry: registry} do 893 name = {:via, Registry, {registry, :name, :value}} 894 spec = %{id: :foo, start: {Agent, :start_link, [fn -> raise "some error" end, [name: name]]}} 895 assert {:error, {error, _childspec}} = start_supervised(spec) 896 assert {%RuntimeError{message: "some error"}, _stacktrace} = error 897 end 898 899 defp register_task(registry, key, value) do 900 parent = self() 901 902 {:ok, task} = 903 Task.start(fn -> 904 send(parent, Registry.register(registry, key, value)) 905 Process.sleep(:infinity) 906 end) 907 908 assert_receive {:ok, owner} 909 {owner, task} 910 end 911 912 defp kill_and_assert_down(pid) do 913 ref = Process.monitor(pid) 914 Process.exit(pid, :kill) 915 assert_receive {:DOWN, ^ref, _, _, _} 916 end 917 918 defp sum_pid_entries(registry, partitions) do 919 Enum.map(0..(partitions - 1), &Module.concat(registry, "PIDPartition#{&1}")) 920 |> sum_ets_entries() 921 end 922 923 defp sum_ets_entries(table_names) do 924 table_names 925 |> Enum.map(&ets_entries/1) 926 |> Enum.sum() 927 end 928 929 defp ets_entries(table_name) do 930 :ets.all() 931 |> Enum.find_value(fn id -> :ets.info(id, :name) == table_name and :ets.info(id, :size) end) 932 end 933end 934