From c592d9f7c5a065ce7f885acdb3578ff38fa27460 Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Sun, 28 Apr 2019 14:00:44 +0900 Subject: [PATCH] quality: Add test cases for get_async messages --- lib/openflow/enums.ex | 285 ++++++++++++++++++++++++++++++ lib/openflow/flow_removed.ex | 2 + lib/openflow/get_async/reply.ex | 96 +++++++--- lib/openflow/get_async/request.ex | 23 ++- lib/openflow/packet_in.ex | 2 + lib/openflow/port_status.ex | 2 + priv/openflow_enum_gen.exs | 21 +++ test/ofp_get_async_test.exs | 60 +++++++ 8 files changed, 458 insertions(+), 33 deletions(-) create mode 100644 test/ofp_get_async_test.exs diff --git a/lib/openflow/enums.ex b/lib/openflow/enums.ex index 11591ad..fac3ba4 100644 --- a/lib/openflow/enums.ex +++ b/lib/openflow/enums.ex @@ -3597,6 +3597,46 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_int(:no_match, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:no_match) + catch + _class, _reason -> :no_match + end + + def to_int(:action, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:action) + catch + _class, _reason -> :action + end + + def to_int(:invalid_ttl, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:invalid_ttl) + catch + _class, _reason -> :invalid_ttl + end + + def to_int(:action_set, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:action_set) + catch + _class, _reason -> :action_set + end + + def to_int(:group, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:group) + catch + _class, _reason -> :group + end + + def to_int(:packet_out, :packet_in_reason_mask) do + packet_in_reason_mask_to_int(:packet_out) + catch + _class, _reason -> :packet_out + end + + def to_int(_int, :packet_in_reason_mask) do + throw(:bad_enum) + end + def to_int(:add, :flow_mod_command) do flow_mod_command_to_int(:add) catch @@ -3705,6 +3745,46 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_int(:idle_timeout, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:idle_timeout) + catch + _class, _reason -> :idle_timeout + end + + def to_int(:hard_timeout, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:hard_timeout) + catch + _class, _reason -> :hard_timeout + end + + def to_int(:delete, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:delete) + catch + _class, _reason -> :delete + end + + def to_int(:group_delete, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:group_delete) + catch + _class, _reason -> :group_delete + end + + def to_int(:meter_delete, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:meter_delete) + catch + _class, _reason -> :meter_delete + end + + def to_int(:eviction, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_int(:eviction) + catch + _class, _reason -> :eviction + end + + def to_int(_int, :flow_removed_reason_mask) do + throw(:bad_enum) + end + def to_int(:add, :port_reason) do port_reason_to_int(:add) catch @@ -3727,6 +3807,28 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_int(:add, :port_reason_mask) do + port_reason_mask_to_int(:add) + catch + _class, _reason -> :add + end + + def to_int(:delete, :port_reason_mask) do + port_reason_mask_to_int(:delete) + catch + _class, _reason -> :delete + end + + def to_int(:modify, :port_reason_mask) do + port_reason_mask_to_int(:modify) + catch + _class, _reason -> :modify + end + + def to_int(_int, :port_reason_mask) do + throw(:bad_enum) + end + def to_int(:add, :group_mod_command) do group_mod_command_to_int(:add) catch @@ -8753,6 +8855,46 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_atom(0x1, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x1) + catch + _class, _reason -> 1 + end + + def to_atom(0x2, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x2) + catch + _class, _reason -> 2 + end + + def to_atom(0x4, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x4) + catch + _class, _reason -> 4 + end + + def to_atom(0x8, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x8) + catch + _class, _reason -> 8 + end + + def to_atom(0x10, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x10) + catch + _class, _reason -> 16 + end + + def to_atom(0x20, :packet_in_reason_mask) do + packet_in_reason_mask_to_atom(0x20) + catch + _class, _reason -> 32 + end + + def to_atom(_, :packet_in_reason_mask) do + throw(:bad_enum) + end + def to_atom(0x0, :flow_mod_command) do flow_mod_command_to_atom(0x0) catch @@ -8861,6 +9003,46 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_atom(0x1, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x1) + catch + _class, _reason -> 1 + end + + def to_atom(0x2, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x2) + catch + _class, _reason -> 2 + end + + def to_atom(0x4, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x4) + catch + _class, _reason -> 4 + end + + def to_atom(0x8, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x8) + catch + _class, _reason -> 8 + end + + def to_atom(0x10, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x10) + catch + _class, _reason -> 16 + end + + def to_atom(0x20, :flow_removed_reason_mask) do + flow_removed_reason_mask_to_atom(0x20) + catch + _class, _reason -> 32 + end + + def to_atom(_, :flow_removed_reason_mask) do + throw(:bad_enum) + end + def to_atom(0x0, :port_reason) do port_reason_to_atom(0x0) catch @@ -8883,6 +9065,28 @@ defmodule Openflow.Enums do throw(:bad_enum) end + def to_atom(0x1, :port_reason_mask) do + port_reason_mask_to_atom(0x1) + catch + _class, _reason -> 1 + end + + def to_atom(0x2, :port_reason_mask) do + port_reason_mask_to_atom(0x2) + catch + _class, _reason -> 2 + end + + def to_atom(0x4, :port_reason_mask) do + port_reason_mask_to_atom(0x4) + catch + _class, _reason -> 4 + end + + def to_atom(_, :port_reason_mask) do + throw(:bad_enum) + end + def to_atom(0x0, :group_mod_command) do group_mod_command_to_atom(0x0) catch @@ -11545,6 +11749,20 @@ defmodule Openflow.Enums do def packet_in_reason_to_atom(0x4), do: :group def packet_in_reason_to_atom(0x5), do: :packet_out def packet_in_reason_to_atom(_), do: throw(:bad_enum) + def packet_in_reason_mask_to_int(:no_match), do: 0x1 + def packet_in_reason_mask_to_int(:action), do: 0x2 + def packet_in_reason_mask_to_int(:invalid_ttl), do: 0x4 + def packet_in_reason_mask_to_int(:action_set), do: 0x8 + def packet_in_reason_mask_to_int(:group), do: 0x10 + def packet_in_reason_mask_to_int(:packet_out), do: 0x20 + def packet_in_reason_mask_to_int(_), do: throw(:bad_enum) + def packet_in_reason_mask_to_atom(0x1), do: :no_match + def packet_in_reason_mask_to_atom(0x2), do: :action + def packet_in_reason_mask_to_atom(0x4), do: :invalid_ttl + def packet_in_reason_mask_to_atom(0x8), do: :action_set + def packet_in_reason_mask_to_atom(0x10), do: :group + def packet_in_reason_mask_to_atom(0x20), do: :packet_out + def packet_in_reason_mask_to_atom(_), do: throw(:bad_enum) def flow_mod_command_to_int(:add), do: 0x0 def flow_mod_command_to_int(:modify), do: 0x1 def flow_mod_command_to_int(:modify_strict), do: 0x2 @@ -11583,6 +11801,20 @@ defmodule Openflow.Enums do def flow_removed_reason_to_atom(0x4), do: :meter_delete def flow_removed_reason_to_atom(0x5), do: :eviction def flow_removed_reason_to_atom(_), do: throw(:bad_enum) + def flow_removed_reason_mask_to_int(:idle_timeout), do: 0x1 + def flow_removed_reason_mask_to_int(:hard_timeout), do: 0x2 + def flow_removed_reason_mask_to_int(:delete), do: 0x4 + def flow_removed_reason_mask_to_int(:group_delete), do: 0x8 + def flow_removed_reason_mask_to_int(:meter_delete), do: 0x10 + def flow_removed_reason_mask_to_int(:eviction), do: 0x20 + def flow_removed_reason_mask_to_int(_), do: throw(:bad_enum) + def flow_removed_reason_mask_to_atom(0x1), do: :idle_timeout + def flow_removed_reason_mask_to_atom(0x2), do: :hard_timeout + def flow_removed_reason_mask_to_atom(0x4), do: :delete + def flow_removed_reason_mask_to_atom(0x8), do: :group_delete + def flow_removed_reason_mask_to_atom(0x10), do: :meter_delete + def flow_removed_reason_mask_to_atom(0x20), do: :eviction + def flow_removed_reason_mask_to_atom(_), do: throw(:bad_enum) def port_reason_to_int(:add), do: 0x0 def port_reason_to_int(:delete), do: 0x1 def port_reason_to_int(:modify), do: 0x2 @@ -11591,6 +11823,14 @@ defmodule Openflow.Enums do def port_reason_to_atom(0x1), do: :delete def port_reason_to_atom(0x2), do: :modify def port_reason_to_atom(_), do: throw(:bad_enum) + def port_reason_mask_to_int(:add), do: 0x1 + def port_reason_mask_to_int(:delete), do: 0x2 + def port_reason_mask_to_int(:modify), do: 0x4 + def port_reason_mask_to_int(_), do: throw(:bad_enum) + def port_reason_mask_to_atom(0x1), do: :add + def port_reason_mask_to_atom(0x2), do: :delete + def port_reason_mask_to_atom(0x4), do: :modify + def port_reason_mask_to_atom(_), do: throw(:bad_enum) def group_mod_command_to_int(:add), do: 0x0 def group_mod_command_to_int(:modify), do: 0x1 def group_mod_command_to_int(:delete), do: 0x2 @@ -12294,6 +12534,10 @@ defmodule Openflow.Enums do Openflow.Utils.int_to_flags([], int, enum_of(:packet_in_reason)) end + def int_to_flags(int, :packet_in_reason_mask) do + Openflow.Utils.int_to_flags([], int, enum_of(:packet_in_reason_mask)) + end + def int_to_flags(int, :flow_mod_command) do Openflow.Utils.int_to_flags([], int, enum_of(:flow_mod_command)) end @@ -12306,10 +12550,18 @@ defmodule Openflow.Enums do Openflow.Utils.int_to_flags([], int, enum_of(:flow_removed_reason)) end + def int_to_flags(int, :flow_removed_reason_mask) do + Openflow.Utils.int_to_flags([], int, enum_of(:flow_removed_reason_mask)) + end + def int_to_flags(int, :port_reason) do Openflow.Utils.int_to_flags([], int, enum_of(:port_reason)) end + def int_to_flags(int, :port_reason_mask) do + Openflow.Utils.int_to_flags([], int, enum_of(:port_reason_mask)) + end + def int_to_flags(int, :group_mod_command) do Openflow.Utils.int_to_flags([], int, enum_of(:group_mod_command)) end @@ -12662,6 +12914,10 @@ defmodule Openflow.Enums do Openflow.Utils.flags_to_int(0, flags, enum_of(:packet_in_reason)) end + def flags_to_int(flags, :packet_in_reason_mask) do + Openflow.Utils.flags_to_int(0, flags, enum_of(:packet_in_reason_mask)) + end + def flags_to_int(flags, :flow_mod_command) do Openflow.Utils.flags_to_int(0, flags, enum_of(:flow_mod_command)) end @@ -12674,10 +12930,18 @@ defmodule Openflow.Enums do Openflow.Utils.flags_to_int(0, flags, enum_of(:flow_removed_reason)) end + def flags_to_int(flags, :flow_removed_reason_mask) do + Openflow.Utils.flags_to_int(0, flags, enum_of(:flow_removed_reason_mask)) + end + def flags_to_int(flags, :port_reason) do Openflow.Utils.flags_to_int(0, flags, enum_of(:port_reason)) end + def flags_to_int(flags, :port_reason_mask) do + Openflow.Utils.flags_to_int(0, flags, enum_of(:port_reason_mask)) + end + def flags_to_int(flags, :group_mod_command) do Openflow.Utils.flags_to_int(0, flags, enum_of(:group_mod_command)) end @@ -13462,6 +13726,16 @@ defmodule Openflow.Enums do defp enum_of(:packet_in_reason), do: [no_match: 0, action: 1, invalid_ttl: 2, action_set: 3, group: 4, packet_out: 5] + defp enum_of(:packet_in_reason_mask), + do: [ + no_match: 1, + action: 2, + invalid_ttl: 4, + action_set: 8, + group: 16, + packet_out: 32 + ] + defp enum_of(:flow_mod_command), do: [add: 0, modify: 1, modify_strict: 2, delete: 3, delete_strict: 4] @@ -13484,7 +13758,18 @@ defmodule Openflow.Enums do eviction: 5 ] + defp enum_of(:flow_removed_reason_mask), + do: [ + idle_timeout: 1, + hard_timeout: 2, + delete: 4, + group_delete: 8, + meter_delete: 16, + eviction: 32 + ] + defp enum_of(:port_reason), do: [add: 0, delete: 1, modify: 2] + defp enum_of(:port_reason_mask), do: [add: 1, delete: 2, modify: 4] defp enum_of(:group_mod_command), do: [add: 0, modify: 1, delete: 2] defp enum_of(:group_type), do: [all: 0, select: 1, indirect: 2, fast_failover: 3] defp enum_of(:group_id), do: [max: 4_294_967_040, all: 4_294_967_292, any: 4_294_967_295] diff --git a/lib/openflow/flow_removed.ex b/lib/openflow/flow_removed.ex index eea87d3..9035804 100644 --- a/lib/openflow/flow_removed.ex +++ b/lib/openflow/flow_removed.ex @@ -19,6 +19,8 @@ defmodule Openflow.FlowRemoved do alias __MODULE__ + @type reason :: :idle_timeout | :hard_timeout | :delete | :group_delete | :meter | :eviction + def ofp_type, do: 11 def read( diff --git a/lib/openflow/get_async/reply.ex b/lib/openflow/get_async/reply.ex index 2f2e1a7..05caa8d 100644 --- a/lib/openflow/get_async/reply.ex +++ b/lib/openflow/get_async/reply.ex @@ -6,41 +6,89 @@ defmodule Openflow.GetAsync.Reply do datapath_id: nil, # virtual field aux_id: 0, - packet_in_mask_master: 0, - packet_in_mask_slave: 0, - port_status_mask_master: 0, - port_status_mask_slave: 0, - flow_removed_mask_master: 0, - flow_removed_mask_slave: 0 + packet_in_mask_master: [], + packet_in_mask_slave: [], + port_status_mask_master: [], + port_status_mask_slave: [], + flow_removed_mask_master: [], + flow_removed_mask_slave: [] ) alias __MODULE__ + alias Openflow.Enums + @type t :: %Reply{ + version: 4, + datapath_id: String.t() | nil, + aux_id: 0..0xF, + xid: 0..0xFFFFFFFF, + packet_in_mask_master: [Openflow.Packet.reason()], + packet_in_mask_slave: [Openflow.Packet.reason()], + port_status_mask_master: [Openflow.Packet.reason()], + port_status_mask_slave: [Openflow.Packet.reason()], + flow_removed_mask_master: [Openflow.Packet.reason()], + flow_removed_mask_slave: [Openflow.Packet.reason()] + } + + @spec ofp_type() :: 27 def ofp_type, do: 27 - def read( - <> - ) do + @spec new( + xid: 0..0xFFFFFFFF, + packet_in_mask_master: [Openflow.Packet.reason()], + packet_in_mask_slave: [Openflow.Packet.reason()], + port_status_mask_master: [Openflow.Packet.reason()], + port_status_mask_slave: [Openflow.Packet.reason()], + flow_removed_mask_master: [Openflow.Packet.reason()], + flow_removed_mask_slave: [Openflow.Packet.reason()] + ) :: t() + def new(options \\ []) do %Reply{ - packet_in_mask_master: packet_in_mask_master, - packet_in_mask_slave: packet_in_mask_slave, - port_status_mask_master: port_status_mask_master, - port_status_mask_slave: port_status_mask_slave, - flow_removed_mask_master: flow_removed_mask_master, - flow_removed_mask_slave: flow_removed_mask_slave + xid: options[:xid] || 0, + packet_in_mask_master: options[:packet_in_mask_master] || [], + packet_in_mask_slave: options[:packet_in_mask_slave] || [], + port_status_mask_master: options[:port_status_mask_master] || [], + port_status_mask_slave: options[:port_status_mask_slave] || [], + flow_removed_mask_master: options[:flow_removed_mask_master] || [], + flow_removed_mask_slave: options[:flow_removed_mask_slave] || [] } end + @spec read(<<_::192>>) :: t() + def read(<< + pin_mask_master::32, + pin_mask_slave::32, + ps_mask_master::32, + ps_mask_slave::32, + fr_mask_master::32, + fr_mask_slave::32 + >>) do + %Reply{ + packet_in_mask_master: Enums.int_to_flags(pin_mask_master, :packet_in_reason_mask), + packet_in_mask_slave: Enums.int_to_flags(pin_mask_slave, :packet_in_reason_mask), + port_status_mask_master: Enums.int_to_flags(ps_mask_master, :port_reason_mask), + port_status_mask_slave: Enums.int_to_flags(ps_mask_slave, :port_reason_mask), + flow_removed_mask_master: Enums.int_to_flags(fr_mask_master, :flow_removed_reason_mask), + flow_removed_mask_slave: Enums.int_to_flags(fr_mask_slave, :flow_removed_reason_mask) + } + end + + @spec to_binary(t()) :: <<_::192>> def to_binary(%Reply{ - packet_in_mask_master: packet_in_mask_master, - packet_in_mask_slave: packet_in_mask_slave, - port_status_mask_master: port_status_mask_master, - port_status_mask_slave: port_status_mask_slave, - flow_removed_mask_master: flow_removed_mask_master, - flow_removed_mask_slave: flow_removed_mask_slave + packet_in_mask_master: pin_mask_master, + packet_in_mask_slave: pin_mask_slave, + port_status_mask_master: ps_mask_master, + port_status_mask_slave: ps_mask_slave, + flow_removed_mask_master: fr_mask_master, + flow_removed_mask_slave: fr_mask_slave }) do - <> + << + Enums.flags_to_int(pin_mask_master, :packet_in_reason_mask)::32, + Enums.flags_to_int(pin_mask_slave, :packet_in_reason_mask)::32, + Enums.flags_to_int(ps_mask_master, :port_reason_mask)::32, + Enums.flags_to_int(ps_mask_slave, :port_reason_mask)::32, + Enums.flags_to_int(fr_mask_master, :flow_removed_reason_mask)::32, + Enums.flags_to_int(fr_mask_slave, :flow_removed_reason_mask)::32 + >> end end diff --git a/lib/openflow/get_async/request.ex b/lib/openflow/get_async/request.ex index 0306095..8c24a3c 100644 --- a/lib/openflow/get_async/request.ex +++ b/lib/openflow/get_async/request.ex @@ -10,17 +10,22 @@ defmodule Openflow.GetAsync.Request do alias __MODULE__ + @type t :: %Request{ + version: 4, + xid: 0..0xFFFFFFFF, + datapath_id: String.t() | nil, + aux_id: 0..0xF | nil + } + + @spec ofp_type() :: 26 def ofp_type, do: 26 - def new(xid \\ 0) do - %Request{xid: xid} - end + @spec new(xid :: 0..0xFFFFFFFF) :: t() + def new(xid \\ 0), do: %Request{xid: xid} - def read(_) do - %Request{} - end + @spec read(any()) :: t() + def read(_), do: %Request{} - def to_binary(%Request{}) do - <<>> - end + @spec to_binary(t()) :: <<>> + def to_binary(%Request{}), do: <<>> end diff --git a/lib/openflow/packet_in.ex b/lib/openflow/packet_in.ex index 2d436e8..c73b2c3 100644 --- a/lib/openflow/packet_in.ex +++ b/lib/openflow/packet_in.ex @@ -16,6 +16,8 @@ defmodule Openflow.PacketIn do alias __MODULE__ + @type reason :: :no_match | :action | :invalid_ttl | :action_set | :group | :packet_out + def ofp_type, do: 10 def read( diff --git a/lib/openflow/port_status.ex b/lib/openflow/port_status.ex index 9b92554..c557e70 100644 --- a/lib/openflow/port_status.ex +++ b/lib/openflow/port_status.ex @@ -10,6 +10,8 @@ defmodule Openflow.PortStatus do alias __MODULE__ + @type reason :: :add | :delete | :modify + def ofp_type, do: 12 def read(<>) do diff --git a/priv/openflow_enum_gen.exs b/priv/openflow_enum_gen.exs index 7666f22..c40e392 100644 --- a/priv/openflow_enum_gen.exs +++ b/priv/openflow_enum_gen.exs @@ -686,6 +686,14 @@ gtpu_extn_sci: 67 group: 4, packet_out: 5 ], + packet_in_reason_mask: [ + no_match: 1 <<< 0, + action: 1 <<< 1, + invalid_ttl: 1 <<< 2, + action_set: 1 <<< 3, + group: 1 <<< 4, + packet_out: 1 <<< 5 + ], flow_mod_command: [ add: 0, modify: 1, @@ -708,11 +716,24 @@ gtpu_extn_sci: 67 meter_delete: 4, eviction: 5 ], + flow_removed_reason_mask: [ + idle_timeout: 1 <<< 0, + hard_timeout: 1 <<< 1, + delete: 1 <<< 2, + group_delete: 1 <<< 3, + meter_delete: 1 <<< 4, + eviction: 1 <<< 5 + ], port_reason: [ add: 0, delete: 1, modify: 2 ], + port_reason_mask: [ + add: 1 <<< 0, + delete: 1 <<< 1, + modify: 1 <<< 2 + ], group_mod_command: [ add: 0, modify: 1, diff --git a/test/ofp_get_async_test.exs b/test/ofp_get_async_test.exs new file mode 100644 index 0000000..9fd7352 --- /dev/null +++ b/test/ofp_get_async_test.exs @@ -0,0 +1,60 @@ +defmodule OfpGetAsyncTest do + use ExUnit.Case + doctest Openflow + + describe "Openflow.GetAsync.Request" do + test "generate and parse without argument" do + get_async = Openflow.GetAsync.Request.new() + + get_async + |> Openflow.to_binary() + |> Openflow.read() + |> Kernel.elem(1) + |> Kernel.==(get_async) + |> assert() + end + + test "generate and parse with argument" do + get_async = Openflow.GetAsync.Request.new(1) + + get_async + |> Openflow.to_binary() + |> Openflow.read() + |> Kernel.elem(1) + |> Kernel.==(get_async) + |> assert() + end + end + + describe "Openflow.GetAsync.Reply" do + test "generate and parse without argument" do + get_async = Openflow.GetAsync.Reply.new() + + get_async + |> Openflow.to_binary() + |> Openflow.read() + |> Kernel.elem(1) + |> Kernel.==(get_async) + |> assert() + end + + test "generate and parse with argument" do + get_async = + Openflow.GetAsync.Reply.new( + flow_removed_mask_master: [:idle_timeout, :hard_timeout, :delete, :group_delete], + flow_removed_mask_slave: [], + packet_in_mask_master: [:no_match, :action], + packet_in_mask_slave: [], + port_status_mask_master: [:add, :delete, :modify], + port_status_mask_slave: [:add, :delete, :modify] + ) + + get_async + |> Openflow.to_binary() + |> Openflow.read() + |> Kernel.elem(1) + |> Kernel.==(get_async) + |> assert() + end + end +end