From 0e463424dbdd7203acb5a68d462bd9748706ae60 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 11 Aug 2019 21:44:46 +0200 Subject: [PATCH] Use gun instead of Websockex. (#150) * Drop WebSockex, use :gun. * Use `:gun` instead of WebsockEx. * Correct typespec. * Update `mix.lock` to new format. * Update lockfile. * Test on latest Elixir & OTP. * Use `:gun.await_up/2` instead of custom receive block. * Add upgrading note. * Make credo happy for now. * Do not block startup. * Reconnect on close frame. * Use new representation for datetimes. * Increase timeouts. * Properly display reconnection. * Handle parrots. * Format the parrots. * Handle unhandled messages. * Read mails at the proper time. * Clear `:gun_down` on WS close. * Let gun reconnect. * Add `fullsweep_after` spawn option. * Log websocket closes. * Drop extra `connect/1`. * Fix someone else's mess. * Use present tense. * Add voice state update functions. * Add missing ). * Remove dots.... * Only warn on heartbeat timeout. * Cancel timer on `:gun_down`. * Send a close frame on `gun_down`. * Close on heartbeat timeout. * Drop periods, add `disconnecting`. --- .travis.yml | 4 +- lib/nostrum/shard/event.ex | 7 +- lib/nostrum/shard/payload.ex | 4 +- lib/nostrum/shard/session.ex | 157 +++++++++++++++++++------------- lib/nostrum/shard/supervisor.ex | 3 +- lib/nostrum/snowflake.ex | 2 +- lib/nostrum/struct/ws_state.ex | 4 +- lib/nostrum/util.ex | 2 +- mix.exs | 2 +- mix.lock | 45 ++++----- test/nostrum/snowflake_test.exs | 4 +- 11 files changed, 136 insertions(+), 98 deletions(-) diff --git a/.travis.yml b/.travis.yml index ca4457155..d57336c75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: elixir elixir: - - '1.7.4' -otp_release: '20.3' + - '1.9.0' +otp_release: '22.0' script: - mix credo --strict diff --git a/lib/nostrum/shard/event.ex b/lib/nostrum/shard/event.ex index 4a1bbf025..830a57e10 100644 --- a/lib/nostrum/shard/event.ex +++ b/lib/nostrum/shard/event.ex @@ -31,9 +31,12 @@ defmodule Nostrum.Shard.Event do end def handle(:hello, payload, state) do - state = %{state | heartbeat_interval: payload.d.heartbeat_interval} + state = %{ + state + | heartbeat_interval: payload.d.heartbeat_interval + } - WebSockex.cast(state.conn_pid, :heartbeat) + GenServer.cast(state.conn_pid, :heartbeat) if session_exists?(state) do Logger.info("RESUMING") diff --git a/lib/nostrum/shard/payload.ex b/lib/nostrum/shard/payload.ex index bc6ef9f5d..c2f4eebd7 100644 --- a/lib/nostrum/shard/payload.ex +++ b/lib/nostrum/shard/payload.ex @@ -79,6 +79,8 @@ defmodule Nostrum.Shard.Payload do defp build_payload(data, opcode_name) do opcode = Constants.opcode_from_name(opcode_name) - %{"op" => opcode, "d" => data} |> :erlang.term_to_binary() + + %{"op" => opcode, "d" => data} + |> :erlang.term_to_binary() end end diff --git a/lib/nostrum/shard/session.ex b/lib/nostrum/shard/session.ex index 011e15eca..51855e387 100644 --- a/lib/nostrum/shard/session.ex +++ b/lib/nostrum/shard/session.ex @@ -1,16 +1,21 @@ defmodule Nostrum.Shard.Session do @moduledoc false - use WebSockex - alias Nostrum.{Constants, Util} alias Nostrum.Shard.{Connector, Event, Payload} alias Nostrum.Struct.WSState require Logger + use GenServer + @gateway_qs "/?compress=zlib-stream&encoding=etf&v=6" + # Maximum time the initial connection may take, in milliseconds. + @timeout_connect 10_000 + # Maximum time the websocket upgrade may take, in milliseconds. + @timeout_ws_upgrade 10_000 + def update_status(pid, status, game, stream, type) do {idle_since, afk} = case status do @@ -22,55 +27,75 @@ defmodule Nostrum.Shard.Session do end payload = Payload.status_update_payload(idle_since, game, stream, status, afk, type) - WebSockex.cast(pid, {:status_update, payload}) + GenServer.cast(pid, {:status_update, payload}) end def update_voice_state(pid, guild_id, channel_id, self_mute, self_deaf) do payload = Payload.update_voice_state_payload(guild_id, channel_id, self_mute, self_deaf) - WebSockex.cast(pid, {:update_voice_state, payload}) + GenServer.cast(pid, {:update_voice_state, payload}) end def request_guild_members(pid, guild_id, limit \\ 0) do payload = Payload.request_members_payload(guild_id, limit) - WebSockex.cast(pid, {:request_guild_members, payload}) + GenServer.cast(pid, {:request_guild_members, payload}) end def start_link([gateway, shard_num]) do + GenServer.start_link(__MODULE__, [gateway, shard_num], spawn_opt: [Util.fullsweep_after()]) + end + + def init([_gateway, _shard_num] = args) do + {:ok, nil, {:continue, args}} + end + + def handle_continue([gateway, shard_num], nil) do + Connector.block_until_connect() + Logger.metadata(shard: shard_num) + + {:ok, worker} = :gun.open(:binary.bin_to_list(gateway), 443, %{protocols: [:http]}) + {:ok, :http} = :gun.await_up(worker, @timeout_connect) + stream = :gun.ws_upgrade(worker, @gateway_qs) + await_ws_upgrade(worker, stream) + + zlib_context = :zlib.open() + :zlib.inflateInit(zlib_context) + state = %WSState{ + conn_pid: self(), + conn: worker, shard_num: shard_num, gateway: gateway <> @gateway_qs, last_heartbeat_ack: DateTime.utc_now(), - heartbeat_ack: true + heartbeat_ack: true, + zlib_ctx: zlib_context } - Connector.block_until_connect() + Logger.debug(fn -> "Websocket connection up on worker #{inspect(worker)}" end) - # TODO: Add support for `spawn_opt` start arguments to WebSockex, this does nothing until then. - WebSockex.start_link( - gateway <> @gateway_qs, - __MODULE__, - state, - spawn_opt: [Util.fullsweep_after()] - ) + {:noreply, state} end - def handle_connect(conn, state) do - Logger.metadata(shard: state.shard_num) + defp await_ws_upgrade(worker, stream) do + # TODO: Once gun 2.0 is released, the block below can be simplified to: + # {:upgrade, [<<"websocket">>], _headers} = :gun.await(worker, stream, @timeout_ws_upgrade) - zlib_ctx = :zlib.open() - :zlib.inflateInit(zlib_ctx) + receive do + {:gun_upgrade, ^worker, ^stream, [<<"websocket">>], _headers} -> + :ok - {:ok, - %{ - state - | conn: conn, - conn_pid: self(), - zlib_ctx: zlib_ctx, - heartbeat_ack: true - }} + {:gun_error, ^worker, ^stream, reason} -> + exit({:ws_upgrade_failed, reason}) + after + @timeout_ws_upgrade -> + Logger.error(fn -> + "Cannot upgrade connection to Websocket after #{@timeout_ws_upgrade / 1000} seconds" + end) + + exit(:timeout) + end end - def handle_frame({:binary, frame}, state) do + def handle_info({:gun_ws, _worker, _stream, {:binary, frame}}, state) do payload = state.zlib_ctx |> :zlib.inflate(frame) @@ -86,63 +111,69 @@ defmodule Nostrum.Shard.Session do case from_handle do {new_state, reply} -> - {:reply, {:binary, reply}, new_state} + :ok = :gun.ws_send(state.conn, {:binary, reply}) + {:noreply, new_state} new_state -> - {:ok, new_state} + {:noreply, new_state} end end + def handle_info({:gun_ws, _conn, _stream, {:close, errno, reason}}, state) do + Logger.info("Shard websocket closed (errno #{errno}, reason #{inspect(reason)})") + {:noreply, state} + end + + def handle_info( + {:gun_down, _conn, _proto, _reason, _killed_streams, _unprocessed_streams}, + state + ) do + # Try to cancel the internal timer, but + # do not explode if it was already cancelled. + :timer.cancel_ref(state.timer_ref) + {:noreply, state} + end + + def handle_info({:gun_up, worker, _proto}, state) do + :ok = :zlib.inflateReset(state.zlib_ctx) + stream = :gun.ws_upgrade(worker, @gateway_qs) + await_ws_upgrade(worker, stream) + Logger.warn("Reconnected after connection broke") + {:noreply, state} + end + def handle_cast({:status_update, payload}, state) do - {:reply, {:binary, payload}, state} + :ok = :gun.ws_send(state.conn, {:binary, payload}) + {:noreply, state} end def handle_cast({:update_voice_state, payload}, state) do - {:reply, {:binary, payload}, state} + :ok = :gun.ws_send(state.conn, {:binary, payload}) + {:noreply, state} end def handle_cast({:request_guild_members, payload}, state) do - {:reply, {:binary, payload}, state} + :ok = :gun.ws_send(state.conn, {:binary, payload}) + {:noreply, state} end def handle_cast(:heartbeat, %{heartbeat_ack: false} = state) do Logger.warn("heartbeat_ack not received in time, disconnecting") - {:close, state} + {:ok, :cancel} = :timer.cancel_ref(state.timer_ref) + :gun.ws_send(state.conn, :close) + {:noreply, state} end def handle_cast(:heartbeat, state) do {:ok, ref} = - :timer.apply_after(state.heartbeat_interval, WebSockex, :cast, [state.conn_pid, :heartbeat]) - - {:reply, {:binary, Payload.heartbeat_payload(state.seq)}, - %{state | heartbeat_ref: ref, heartbeat_ack: false, last_heartbeat_send: DateTime.utc_now()}} - end - - def handle_disconnect(%{reason: reason}, state) when is_tuple(reason) do - Logger.warn(fn -> - "websocket disconnected with reason #{inspect(reason)}, attempting reconnect" - end) + :timer.apply_after(state.heartbeat_interval, :gen_server, :cast, [ + state.conn_pid, + :heartbeat + ]) - :timer.cancel(state.heartbeat_ref) + :ok = :gun.ws_send(state.conn, {:binary, Payload.heartbeat_payload(state.seq)}) - {:reconnect, state} - end - - def handle_disconnect(%{reason: reason}, state) do - Logger.warn(fn -> - "websocket errored with reason #{inspect(reason)}, attempting reconnect" - end) - - :timer.cancel(state.heartbeat_ref) - - {:reconnect, state} - end - - def terminate(reason, state) do - :timer.cancel(state.heartbeat_ref) - - Logger.warn(fn -> - "websocket closed with reason #{inspect(reason)}" - end) + {:noreply, + %{state | heartbeat_ref: ref, heartbeat_ack: false, last_heartbeat_send: DateTime.utc_now()}} end end diff --git a/lib/nostrum/shard/supervisor.ex b/lib/nostrum/shard/supervisor.ex index ece5ca086..42a34f53c 100644 --- a/lib/nostrum/shard/supervisor.ex +++ b/lib/nostrum/shard/supervisor.ex @@ -3,11 +3,12 @@ defmodule Nostrum.Shard.Supervisor do use Supervisor - alias Nostrum.{Shard, Util} alias Nostrum.Cache.Mapping.GuildShard alias Nostrum.Error.CacheError + alias Nostrum.Shard alias Nostrum.Shard.Session alias Nostrum.Shard.Stage.{Cache, Producer} + alias Nostrum.Util require Logger diff --git a/lib/nostrum/snowflake.ex b/lib/nostrum/snowflake.ex index 8791191d3..10fd30a26 100644 --- a/lib/nostrum/snowflake.ex +++ b/lib/nostrum/snowflake.ex @@ -155,7 +155,7 @@ defmodule Nostrum.Snowflake do ```Elixir iex> Nostrum.Snowflake.creation_time(177888205536886784) - #DateTime<2016-05-05 21:04:13.203Z> + ~U[2016-05-05 21:04:13.203Z] ``` """ @spec creation_time(t) :: DateTime.t() diff --git a/lib/nostrum/struct/ws_state.ex b/lib/nostrum/struct/ws_state.ex index 423396231..022bcc10e 100644 --- a/lib/nostrum/struct/ws_state.ex +++ b/lib/nostrum/struct/ws_state.ex @@ -31,8 +31,8 @@ defmodule Nostrum.Struct.WSState do @typedoc "PID of the shard containing this state" @type shard_pid :: pid - @typedoc "Websockex connection state map" - @type conn :: map + @typedoc "PID of the `:gun` worker connected to the websocket" + @type conn :: pid @typedoc "PID of the connection process" @type conn_pid :: pid diff --git a/lib/nostrum/util.ex b/lib/nostrum/util.ex index 91d568bef..7b11d9d03 100644 --- a/lib/nostrum/util.ex +++ b/lib/nostrum/util.ex @@ -138,7 +138,7 @@ defmodule Nostrum.Util do {:ok, body} -> body = Poison.decode!(body) - url = body["url"] + "wss://" <> url = body["url"] shards = if body["shards"], do: body["shards"], else: 1 :ets.insert(:gateway_url, {"url", url, shards}) diff --git a/mix.exs b/mix.exs index 9c665e0c5..834dc63b5 100644 --- a/mix.exs +++ b/mix.exs @@ -79,10 +79,10 @@ defmodule Nostrum.Mixfile do [ {:httpoison, "~> 1.5"}, {:poison, "~> 3.0"}, + {:gun, "~> 1.3"}, {:ex_doc, "~> 0.14", only: :dev}, {:credo, "~> 0.4", only: [:dev, :test]}, {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, - {:websockex, "~> 0.4"}, {:gen_stage, "~> 0.11"}, {:recon, "~> 2.3", only: :dev} ] diff --git a/mix.lock b/mix.lock index c0de0005c..3afad4cf4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,25 @@ %{ - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, - "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, optional: false]}]}, - "credo": {:hex, :credo, "0.10.2", "03ad3a1eff79a16664ed42fc2975b5e5d0ce243d69318060c626c34720a49512", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}, {:jason, "~> 1.0", [hex: :jason, optional: false]}]}, - "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], []}, - "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], []}, - "ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, optional: false]}]}, - "gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], []}, - "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, optional: false]}, {:idna, "6.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, optional: false]}]}, - "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, optional: false]}]}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, optional: false]}]}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, optional: true]}]}, - "makeup": {:hex, :makeup, "0.6.0", "e0fd985525e8d42352782bd76253105fbab0a783ac298708ca9020636c9568af", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, optional: false]}]}, - "makeup_elixir": {:hex, :makeup_elixir, "0.11.0", "aa3446f67356afa5801618867587a8863f176f9c632fb62b20f49bd1ea335e8a", [:mix], [{:makeup, "~> 0.6", [hex: :makeup, optional: false]}]}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], []}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], []}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, - "recon": {:hex, :recon, "2.4.0", "901ff78b39c754fb4d6fd72dcf0dbd398967bbd2e4d59c08d4d7aa44a73de91d", [:rebar3], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], []}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], []}, - "websockex": {:hex, :websockex, "0.4.2", "9a3b7dc25655517ecd3f8ff7109a77fce94956096b942836cdcfbc7c86603ecc", [:mix], []}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "2.6.0", "8aa629f81a0fc189f261dc98a42243fa842625feea3c7ec56c48f4ccdb55490f", [:rebar3], [], "hexpm"}, + "credo": {:hex, :credo, "0.10.2", "03ad3a1eff79a16664ed42fc2975b5e5d0ce243d69318060c626c34720a49512", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "gen_stage": {:hex, :gen_stage, "0.14.2", "6a2a578a510c5bfca8a45e6b27552f613b41cf584b58210f017088d3d17d0b14", [:mix], [], "hexpm"}, + "gun": {:hex, :gun, "1.3.0", "18e5d269649c987af95aec309f68a27ffc3930531dd227a6eaa0884d6684286e", [:rebar3], [{:cowlib, "~> 2.6.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/test/nostrum/snowflake_test.exs b/test/nostrum/snowflake_test.exs index d726ce847..0d4c9378a 100644 --- a/test/nostrum/snowflake_test.exs +++ b/test/nostrum/snowflake_test.exs @@ -12,8 +12,8 @@ defmodule Nostrum.SnowflakeTest do min_datetime = Snowflake.creation_time(0) max_datetime = Snowflake.creation_time(0xFFFFFFFFFFFFFFFF) - assert(inspect(min_datetime) === "#DateTime<2015-01-01 00:00:00.000Z>") - assert(inspect(max_datetime) === "#DateTime<2154-05-15 07:35:11.103Z>") + assert(inspect(min_datetime) === "~U[2015-01-01 00:00:00.000Z]") + assert(inspect(max_datetime) === "~U[2154-05-15 07:35:11.103Z]") end end end