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