X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=test%2Fpool%2Fconnections_test.exs;h=aeda54875851f171f06505596a7586ce6b02db61;hb=7f692343c80ddf353712490edfbcdb14866f5685;hp=6f0e041ae7a7640b8675c55720c758cb58a388d5;hpb=13918cb545d583decfb728300850b307c59e2031;p=akkoma diff --git a/test/pool/connections_test.exs b/test/pool/connections_test.exs index 6f0e041ae..aeda54875 100644 --- a/test/pool/connections_test.exs +++ b/test/pool/connections_test.exs @@ -1,32 +1,85 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Pool.ConnectionsTest do - use ExUnit.Case + use ExUnit.Case, async: true use Pleroma.Tests.Helpers + import ExUnit.CaptureLog - alias Pleroma.Gun.API + import Mox + alias Pleroma.Gun.Conn + alias Pleroma.GunMock alias Pleroma.Pool.Connections - setup_all do - {:ok, _} = Registry.start_link(keys: :unique, name: API.Mock) - :ok - end + setup :verify_on_exit! - setup do + setup_all do name = :test_connections - adapter = Application.get_env(:tesla, :adapter) - Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun) - on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end) + {:ok, pid} = Connections.start_link({name, [checkin_timeout: 150]}) + {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.GunMock) - {:ok, _pid} = - Connections.start_link({name, [max_connections: 2, receive_connection_timeout: 1_500]}) + on_exit(fn -> + if Process.alive?(pid), do: GenServer.stop(name) + end) {:ok, name: name} end + defp open_mock(num \\ 1) do + GunMock + |> expect(:open, num, &start_and_register(&1, &2, &3)) + |> expect(:await_up, num, fn _, _ -> {:ok, :http} end) + |> expect(:set_owner, num, fn _, _ -> :ok end) + end + + defp connect_mock(mock) do + mock + |> expect(:connect, &connect(&1, &2)) + |> expect(:await, &await(&1, &2)) + end + + defp info_mock(mock), do: expect(mock, :info, &info(&1)) + + defp start_and_register('gun-not-up.com', _, _), do: {:error, :timeout} + + defp start_and_register(host, port, _) do + {:ok, pid} = Task.start_link(fn -> Process.sleep(1000) end) + + scheme = + case port do + 443 -> "https" + _ -> "http" + end + + Registry.register(GunMock, pid, %{ + origin_scheme: scheme, + origin_host: host, + origin_port: port + }) + + {:ok, pid} + end + + defp info(pid) do + [{_, info}] = Registry.lookup(GunMock, pid) + info + end + + defp connect(pid, _) do + ref = make_ref() + Registry.register(GunMock, ref, pid) + ref + end + + defp await(pid, ref) do + [{_, ^pid}] = Registry.lookup(GunMock, ref) + {:response, :fin, 200, []} + end + + defp now, do: :os.system_time(:second) + describe "alive?/2" do test "is alive", %{name: name} do assert Connections.alive?(name) @@ -38,10 +91,11 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "opens connection and reuse it on next request", %{name: name} do + open_mock() url = "http://some-domain.com" key = "http:some-domain.com:80" refute Connections.checkin(url, name) - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) assert is_pid(conn) @@ -103,10 +157,11 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "reuse connection for idna domains", %{name: name} do + open_mock() url = "http://ですsome-domain.com" refute Connections.checkin(url, name) - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) assert is_pid(conn) @@ -131,11 +186,12 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "reuse for ipv4", %{name: name} do + open_mock() url = "http://127.0.0.1" refute Connections.checkin(url, name) - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) assert is_pid(conn) @@ -174,11 +230,12 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "reuse for ipv6", %{name: name} do + open_mock() url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]" refute Connections.checkin(url, name) - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) assert is_pid(conn) @@ -203,9 +260,13 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "up and down ipv4", %{name: name} do + open_mock() + |> info_mock() + |> allow(self(), name) + self = self() url = "http://127.0.0.1" - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) send(name, {:gun_down, conn, nil, nil, nil}) send(name, {:gun_up, conn, nil}) @@ -224,8 +285,13 @@ defmodule Pleroma.Pool.ConnectionsTest do test "up and down ipv6", %{name: name} do self = self() + + open_mock() + |> info_mock() + |> allow(self, name) + url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]" - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) send(name, {:gun_down, conn, nil, nil, nil}) send(name, {:gun_up, conn, nil}) @@ -243,19 +309,20 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "reuses connection based on protocol", %{name: name} do + open_mock(2) http_url = "http://some-domain.com" http_key = "http:some-domain.com:80" https_url = "https://some-domain.com" https_key = "https:some-domain.com:443" refute Connections.checkin(http_url, name) - :ok = Connections.open_conn(http_url, name) + :ok = Conn.open(http_url, name) conn = Connections.checkin(http_url, name) assert is_pid(conn) assert Process.alive?(conn) refute Connections.checkin(https_url, name) - :ok = Connections.open_conn(https_url, name) + :ok = Conn.open(https_url, name) https_conn = Connections.checkin(https_url, name) refute conn == https_conn @@ -281,20 +348,26 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "connection can't get up", %{name: name} do + expect(GunMock, :open, &start_and_register(&1, &2, &3)) url = "http://gun-not-up.com" assert capture_log(fn -> - :ok = Connections.open_conn(url, name) + refute Conn.open(url, name) refute Connections.checkin(url, name) end) =~ - "Received error on opening connection http://gun-not-up.com: {:error, :timeout}" + "Opening connection to http://gun-not-up.com failed with error {:error, :timeout}" end test "process gun_down message and then gun_up", %{name: name} do self = self() + + open_mock() + |> info_mock() + |> allow(self, name) + url = "http://gun-down-and-up.com" key = "http:gun-down-and-up.com:80" - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) conn = Connections.checkin(url, name) assert is_pid(conn) @@ -342,8 +415,9 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "async processes get same conn for same domain", %{name: name} do + open_mock() url = "http://some-domain.com" - :ok = Connections.open_conn(url, name) + :ok = Conn.open(url, name) tasks = for _ <- 1..5 do @@ -374,11 +448,12 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "remove frequently used and idle", %{name: name} do + open_mock(3) self = self() http_url = "http://some-domain.com" https_url = "https://some-domain.com" - :ok = Connections.open_conn(https_url, name) - :ok = Connections.open_conn(http_url, name) + :ok = Conn.open(https_url, name) + :ok = Conn.open(http_url, name) conn1 = Connections.checkin(https_url, name) @@ -409,7 +484,7 @@ defmodule Pleroma.Pool.ConnectionsTest do :ok = Connections.checkout(conn1, self, name) another_url = "http://another-domain.com" - :ok = Connections.open_conn(another_url, name) + :ok = Conn.open(another_url, name) conn = Connections.checkin(another_url, name) %Connections{ @@ -426,302 +501,14 @@ defmodule Pleroma.Pool.ConnectionsTest do } = Connections.get_state(name) end - describe "integration test" do - @describetag :integration - - clear_config([API]) do - Pleroma.Config.put([API], Pleroma.Gun) - end - - test "opens connection and reuse it on next request", %{name: name} do - url = "http://httpbin.org" - :ok = Connections.open_conn(url, name) - Process.sleep(250) - conn = Connections.checkin(url, name) - - assert is_pid(conn) - assert Process.alive?(conn) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - - %Connections{ - conns: %{ - "http:httpbin.org:80" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - test "opens ssl connection and reuse it on next request", %{name: name} do - url = "https://httpbin.org" - :ok = Connections.open_conn(url, name) - Process.sleep(1_000) - conn = Connections.checkin(url, name) - - assert is_pid(conn) - assert Process.alive?(conn) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - test "remove frequently used and idle", %{name: name} do - self = self() - https1 = "https://www.google.com" - https2 = "https://httpbin.org" - - :ok = Connections.open_conn(https1, name) - :ok = Connections.open_conn(https2, name) - Process.sleep(1_500) - conn = Connections.checkin(https1, name) - - for _ <- 1..4 do - Connections.checkin(https2, name) - end - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: _, - gun_state: :up - }, - "https:www.google.com:443" => %Conn{ - conn: _, - gun_state: :up - } - } - } = Connections.get_state(name) - - :ok = Connections.checkout(conn, self, name) - http = "http://httpbin.org" - Process.sleep(1_000) - :ok = Connections.open_conn(http, name) - conn = Connections.checkin(http, name) - - %Connections{ - conns: %{ - "http:httpbin.org:80" => %Conn{ - conn: ^conn, - gun_state: :up - }, - "https:httpbin.org:443" => %Conn{ - conn: _, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - test "remove earlier used and idle", %{name: name} do - self = self() - - https1 = "https://www.google.com" - https2 = "https://httpbin.org" - :ok = Connections.open_conn(https1, name) - :ok = Connections.open_conn(https2, name) - Process.sleep(1_500) - - Connections.checkin(https1, name) - conn = Connections.checkin(https1, name) - - Process.sleep(1_000) - Connections.checkin(https2, name) - Connections.checkin(https2, name) - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: _, - gun_state: :up - }, - "https:www.google.com:443" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - :ok = Connections.checkout(conn, self, name) - :ok = Connections.checkout(conn, self, name) - - http = "http://httpbin.org" - :ok = Connections.open_conn(http, name) - Process.sleep(1_000) - - conn = Connections.checkin(http, name) - - %Connections{ - conns: %{ - "http:httpbin.org:80" => %Conn{ - conn: ^conn, - gun_state: :up - }, - "https:httpbin.org:443" => %Conn{ - conn: _, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - test "doesn't open new conn on pool overflow", %{name: name} do - self = self() - - https1 = "https://www.google.com" - https2 = "https://httpbin.org" - :ok = Connections.open_conn(https1, name) - :ok = Connections.open_conn(https2, name) - Process.sleep(1_000) - Connections.checkin(https1, name) - conn1 = Connections.checkin(https1, name) - conn2 = Connections.checkin(https2, name) - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: ^conn2, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}] - }, - "https:www.google.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}, {^self, _}] - } - } - } = Connections.get_state(name) - - refute Connections.checkin("http://httpbin.org", name) - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: ^conn2, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}] - }, - "https:www.google.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}, {^self, _}] - } - } - } = Connections.get_state(name) - end - - test "get idle connection with the smallest crf", %{ - name: name - } do - self = self() - - https1 = "https://www.google.com" - https2 = "https://httpbin.org" - - :ok = Connections.open_conn(https1, name) - :ok = Connections.open_conn(https2, name) - Process.sleep(1_500) - Connections.checkin(https1, name) - Connections.checkin(https2, name) - Connections.checkin(https1, name) - conn1 = Connections.checkin(https1, name) - conn2 = Connections.checkin(https2, name) - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: ^conn2, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}, {^self, _}], - crf: crf2 - }, - "https:www.google.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}, {^self, _}, {^self, _}], - crf: crf1 - } - } - } = Connections.get_state(name) - - assert crf1 > crf2 - - :ok = Connections.checkout(conn1, self, name) - :ok = Connections.checkout(conn1, self, name) - :ok = Connections.checkout(conn1, self, name) - - :ok = Connections.checkout(conn2, self, name) - :ok = Connections.checkout(conn2, self, name) - - %Connections{ - conns: %{ - "https:httpbin.org:443" => %Conn{ - conn: ^conn2, - gun_state: :up, - conn_state: :idle, - used_by: [] - }, - "https:www.google.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :idle, - used_by: [] - } - } - } = Connections.get_state(name) - - http = "http://httpbin.org" - :ok = Connections.open_conn(http, name) - Process.sleep(1_000) - conn = Connections.checkin(http, name) - - %Connections{ - conns: %{ - "https:www.google.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :idle, - used_by: [], - crf: crf1 - }, - "http:httpbin.org:80" => %Conn{ - conn: ^conn, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}], - crf: crf - } - } - } = Connections.get_state(name) - - assert crf1 > crf - end - end - describe "with proxy" do test "as ip", %{name: name} do + open_mock() + |> connect_mock() + url = "http://proxy-string.com" key = "http:proxy-string.com:80" - :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123}) + :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123}) conn = Connections.checkin(url, name) @@ -740,8 +527,11 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "as host", %{name: name} do + open_mock() + |> connect_mock() + url = "http://proxy-tuple-atom.com" - :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050}) + :ok = Conn.open(url, name, proxy: {'localhost', 9050}) conn = Connections.checkin(url, name) %Connections{ @@ -759,9 +549,12 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "as ip and ssl", %{name: name} do + open_mock() + |> connect_mock() + url = "https://proxy-string.com" - :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123}) + :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123}) conn = Connections.checkin(url, name) %Connections{ @@ -779,8 +572,11 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "as host and ssl", %{name: name} do + open_mock() + |> connect_mock() + url = "https://proxy-tuple-atom.com" - :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050}) + :ok = Conn.open(url, name, proxy: {'localhost', 9050}) conn = Connections.checkin(url, name) %Connections{ @@ -798,9 +594,11 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "with socks type", %{name: name} do + open_mock() + url = "http://proxy-socks.com" - :ok = Connections.open_conn(url, name, proxy: {:socks5, 'localhost', 1234}) + :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234}) conn = Connections.checkin(url, name) @@ -819,9 +617,10 @@ defmodule Pleroma.Pool.ConnectionsTest do end test "with socks4 type and ssl", %{name: name} do + open_mock() url = "https://proxy-socks.com" - :ok = Connections.open_conn(url, name, proxy: {:socks4, 'localhost', 1234}) + :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234}) conn = Connections.checkin(url, name) @@ -888,72 +687,74 @@ defmodule Pleroma.Pool.ConnectionsTest do end describe "get_unused_conns/1" do - test "crf is equalent, sorting by reference" do - conns = %{ - "1" => %Conn{ - conn_state: :idle, - last_reference: now() - 1 - }, - "2" => %Conn{ - conn_state: :idle, - last_reference: now() - } - } - - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns) + test "crf is equalent, sorting by reference", %{name: name} do + Connections.add_conn(name, "1", %Conn{ + conn_state: :idle, + last_reference: now() - 1 + }) + + Connections.add_conn(name, "2", %Conn{ + conn_state: :idle, + last_reference: now() + }) + + assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) end - test "reference is equalent, sorting by crf" do - conns = %{ - "1" => %Conn{ - conn_state: :idle, - crf: 1.999 - }, - "2" => %Conn{ - conn_state: :idle, - crf: 2 - } - } + test "reference is equalent, sorting by crf", %{name: name} do + Connections.add_conn(name, "1", %Conn{ + conn_state: :idle, + crf: 1.999 + }) + + Connections.add_conn(name, "2", %Conn{ + conn_state: :idle, + crf: 2 + }) - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns) + assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) end - test "higher crf and lower reference" do - conns = %{ - "1" => %Conn{ - conn_state: :idle, - crf: 3, - last_reference: now() - 1 - }, - "2" => %Conn{ - conn_state: :idle, - crf: 2, - last_reference: now() - } - } + test "higher crf and lower reference", %{name: name} do + Connections.add_conn(name, "1", %Conn{ + conn_state: :idle, + crf: 3, + last_reference: now() - 1 + }) - assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(conns) + Connections.add_conn(name, "2", %Conn{ + conn_state: :idle, + crf: 2, + last_reference: now() + }) + + assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name) end - test "lower crf and lower reference" do - conns = %{ - "1" => %Conn{ - conn_state: :idle, - crf: 1.99, - last_reference: now() - 1 - }, - "2" => %Conn{ - conn_state: :idle, - crf: 2, - last_reference: now() - } - } + test "lower crf and lower reference", %{name: name} do + Connections.add_conn(name, "1", %Conn{ + conn_state: :idle, + crf: 1.99, + last_reference: now() - 1 + }) + + Connections.add_conn(name, "2", %Conn{ + conn_state: :idle, + crf: 2, + last_reference: now() + }) - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns) + assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) end end - defp now do - :os.system_time(:second) + test "count/1" do + name = :test_count + {:ok, _} = Connections.start_link({name, [checkin_timeout: 150]}) + assert Connections.count(name) == 0 + Connections.add_conn(name, "1", %Conn{conn: self()}) + assert Connections.count(name) == 1 + Connections.remove_conn(name, "1") + assert Connections.count(name) == 0 end end