Add Mix recompilation from IEx

Add Mix recompilation from IEx

defmodule IEx.Helpers do
  @moduledoc """
  Welcome to Interactive Elixir. You are currently
  seeing the documentation for the module `IEx.Helpers`
  which provides many helpers to make Elixir's shell
  more joyful to work with.

  This message was triggered by invoking the helper
  `h()`, usually referred to as `h/0` (since it expects 0
  arguments).

  There are many other helpers available:

    * `b/1`           - prints callbacks info and docs for a given module
    * `c/2`           - compiles a file at the given path
    * `cd/1`          - changes the current directory
    * `clear/0`       - clears the screen
    * `flush/0`       - flushes all messages sent to the shell
    * `h/0`           - prints this help message
    * `h/1`           - prints help for the given module, function or macro
    * `import_file/1` - evaluates the given file in the shell's context
    * `l/1`           - loads the given module's beam code
    * `ls/0`          - lists the contents of the current directory
    * `ls/1`          - lists the contents of the specified directory
    * `pid/3`         - creates a PID with the 3 integer arguments passed
    * `pwd/0`         - prints the current working directory
    * `r/1`           - recompiles and reloads the given module
    * `recompile/0`   - recompiles the current Mix project (requires iex -S mix)
    * `respawn/0`     - respawns a new IEx shell
    * `s/1`           - prints spec information
    * `t/1`           - prints type information
    * `v/0`           - retrieves the last value from the history
    * `v/1`           - retrieves the nth value from the history

  Help for functions in this module can be consulted
  directly from the command line, as an example, try:

      h(c/2)

  You can also retrieve the documentation for any module
  or function. Try these:

      h(Enum)
      h(Enum.reverse/1)

  To discover all available functions for a module, type the module name
  followed by a dot, then press tab to trigger autocomplete. For example:

      Enum.

  To learn more about IEx as a whole, just type `h(IEx)`.
  """

  require Logger
  import IEx, only: [dont_display_result: 0]

  @doc """
  Recompiles the current Mix application.

  This helper only works when IEx is started with a Mix
  project, for example, `iex -S mix`. Before compiling
  the code, it will stop the current application, and
  start it again afterwards. Stopping applications are
  required so processes in the supervision tree won't
  crash when code is upgraded multiple times without
  going through the proper hot-code swapping mechanism.

  Changes to `mix.exs` or configuration files won't be
  picked up by this helper, only changes to sources.
  Restarting the shell and Mix is required in such cases.

  If you want to reload a single module, consider using
  `r ModuleName` instead.

  NOTE: This feature is experimental and may be removed
  in upcoming releases.
  """
  def recompile do
    if mix_started? do
      config = Mix.Project.config
      reenable_tasks(config)
      stop_apps(config)
      Mix.Task.run("app.start")
    else
      IO.puts IEx.color(:eval_error, "Mix is not running. Please start IEx with: iex -S mix")
      dont_display_result
    end
  end

  defp mix_started? do
    List.keyfind(Application.started_applications, :mix, 0) != nil
  end

  defp reenable_tasks(config) do
    Mix.Task.reenable("app.start")
    Mix.Task.reenable("compile")
    Mix.Task.reenable("compile.all")
    compilers = config[:compilers] || Mix.compilers
    Enum.each compilers, &Mix.Task.reenable("compile.#{&1}")
  end

  defp stop_apps(config) do
    apps =
      cond do
        Mix.Project.umbrella?(config) ->
          for %Mix.Dep{app: app} <- Mix.Dep.Umbrella.loaded, do: app
        app = config[:app] ->
          [app]
        true ->
          []
      end
    Enum.each apps, &Application.stop/1
  end

  @doc """
  Compiles the given files.

  It expects a list of files to compile and an optional path to write
  the compiled code to (defaults to the current directory). When compiling
  one file, there is no need to wrap it in a list.

  It returns the name of the compiled modules.

  If you want to recompile an existing module, check `r/1` instead.

  ## Examples

      c ["foo.ex", "bar.ex"], "ebin"
      #=> [Foo, Bar]

      c "baz.ex"
      #=> [Baz]
  """
  def c(files, path \\ ".") when is_binary(path) do
    files = List.wrap(files)

    unless Enum.all?(files, &is_binary/1) do
      raise ArgumentError, "expected a binary or a list of binaries as argument"
    end

    {found, not_found} =
      files
      |> Enum.map(&Path.expand(&1, path))
      |> Enum.partition(&File.exists?/1)

    unless Enum.empty?(not_found) do
      raise ArgumentError, "could not find files #{Enum.join(not_found, ", ")}"
    end

    {erls, exs} = Enum.partition(found, &String.ends_with?(&1, ".erl"))

    modules = Enum.map(erls, fn(source) ->
      {module, binary} = compile_erlang(source)
      base = source |> Path.basename |> Path.rootname
      File.write!(Path.join(path, base <> ".beam"), binary)
      module
    end)

    modules ++ Kernel.ParallelCompiler.files_to_path(exs, path)
  end

  @doc """
  Clears the console screen.

  This function only works if ANSI escape codes are enabled
  on the shell, which means this function is by default
  unavailable on Windows machines.
  """
  def clear do
    if IO.ANSI.enabled? do
      IO.write [IO.ANSI.home, IO.ANSI.clear]
    else
      IO.puts "Cannot clear the screen because ANSI escape codes are not enabled on this shell"
    end
    dont_display_result
  end

  @doc """
  Prints the documentation for `IEx.Helpers`.
  """
  def h() do
    IEx.Introspection.h(IEx.Helpers)
    dont_display_result
  end

  @doc """
  Prints the documentation for the given module
  or for the given function/arity pair.

  ## Examples

      h(Enum)
      #=> Prints documentation for Enum

  It also accepts functions in the format `fun/arity`
  and `module.fun/arity`, for example:

      h receive/1
      h Enum.all?/2
      h Enum.all?

  """
  @h_modules [__MODULE__, Kernel, Kernel.SpecialForms]

  defmacro h(term)
  defmacro h({:/, _, [call, arity]} = term) do
    args =
      case Macro.decompose_call(call) do
        {_mod, :__info__, []} when arity == 1 ->
          [Module, :__info__, 1]
        {mod, fun, []} ->
          [mod, fun, arity]
        {fun, []} ->
          [@h_modules, fun, arity]
        _ ->
          [term]
      end

    quote do
      IEx.Introspection.h(unquote_splicing(args))
    end
  end

  defmacro h(call) do
    args =
      case Macro.decompose_call(call) do
        {_mod, :__info__, []} ->
          [Module, :__info__, 1]
        {mod, fun, []} ->
          [mod, fun]
        {fun, []} ->
          [@h_modules, fun]
        _ ->
          [call]
      end

    quote do
      IEx.Introspection.h(unquote_splicing(args))
    end
  end

  @doc """
  Prints the documentation for the given callback function.

  It also accepts single module argument to list
  all available behaviour callbacks.

  ## Examples

      b(Mix.Task.run/1)
      b(Mix.Task.run)
      b(Dict)

  """
  defmacro b(term)
  defmacro b({:/, _, [{{:., _, [mod, fun]}, _, []}, arity]}) do
    quote do
      IEx.Introspection.b(unquote(mod), unquote(fun), unquote(arity))
    end
  end

  defmacro b({{:., _, [mod, fun]}, _, []}) do
    quote do
      IEx.Introspection.b(unquote(mod), unquote(fun))
    end
  end

  defmacro b(module) do
    quote do
      IEx.Introspection.b(unquote(module))
    end
  end

  @doc """
  Prints the types for the given module or for the given function/arity pair.

  ## Examples

      t(Enum)
      t(Enum.t/0)
      t(Enum.t)
  """
  defmacro t(term)
  defmacro t({:/, _, [{{:., _, [mod, fun]}, _, []}, arity]}) do
    quote do
      IEx.Introspection.t(unquote(mod), unquote(fun), unquote(arity))
    end
  end

  defmacro t({{:., _, [mod, fun]}, _, []}) do
    quote do
      IEx.Introspection.t(unquote(mod), unquote(fun))
    end
  end

  defmacro t(module) do
    quote do
      IEx.Introspection.t(unquote(module))
    end
  end

  @doc """
  Prints the specs for the given module or for the given function/arity pair.

  ## Examples

      s(Enum)
      s(Enum.all?)
      s(Enum.all?/2)
      s(is_atom)
      s(is_atom/1)

  """
  defmacro s(term)
  defmacro s({:/, _, [call, arity]} = term) do
    args =
      case Macro.decompose_call(call) do
        {mod, fun, []} -> [mod, fun, arity]
        {fun, []} -> [Kernel, fun, arity]
        _ -> [term]
      end

    quote do
      IEx.Introspection.s(unquote_splicing(args))
    end
  end

  defmacro s(call) do
    args =
      case Macro.decompose_call(call) do
        {mod, fun, []} -> [mod, fun]
        {fun, []} -> [Kernel, fun]
        _ -> [call]
      end

    quote do
      IEx.Introspection.s(unquote_splicing(args))
    end
  end

  @doc """
  Retrieves the nth expression's value from the history.

  Use negative values to lookup expression values relative to the current one.
  For instance, v(-1) returns the result of the last evaluated expression.
  """
  def v(n \\ -1) do
    IEx.History.nth(history, n) |> elem(2)
  end

  @doc """
  Recompiles and reloads the given `module`.

  Please note that all the modules defined in the same
  file as `module` are recompiled and reloaded.

  ## In-memory reloading

  When we reload the module in IEx, we recompile the module source code,
  updating its contents in memory. The original `.beam` file in disk,
  probably the one where the first definition of the module came from,
  does not change at all.

  Since typespecs and docs are loaded from the .beam file (they are not
  loaded in memory with the module because there is no need for them to
  be in memory), they are not reloaded when you reload the module.
  """
  def r(module) when is_atom(module) do
    {:reloaded, module, do_r(module)}
  end

  defp do_r(module) do
    unless Code.ensure_loaded?(module) do
      raise ArgumentError, "could not load nor find module: #{inspect module}"
    end

    source = source(module)
    cond do
      source == nil ->
        raise ArgumentError, "could not find source for module: #{inspect module}"

      not File.exists?(source) ->
        raise ArgumentError, "could not find source (#{source}) for module: #{inspect module}"

      String.ends_with?(source, ".erl") ->
        [compile_erlang(source) |> elem(0)]

      true ->
        Enum.map(Code.load_file(source), fn {name, _} -> name end)
    end
  end

  @doc """
  Loads the given module's beam code (and ensures any previous
  old version was properly purged before).

  This function is useful when you know the bytecode for module
  has been updated in the filesystem and you want to tell the VM
  to load it.
  """
  def l(module) when is_atom(module) do
    :code.purge(module)
    :code.load_file(module)
  end

  @doc """
  Flushes all messages sent to the shell and prints them out.
  """
  def flush do
    do_flush(IEx.inspect_opts)
  end

  defp do_flush(inspect_opts) do
    receive do
      msg ->
        IO.inspect(msg, inspect_opts)
        do_flush(inspect_opts)
    after
      0 -> :ok
    end
  end

  defp source(module) do
    source = module.module_info(:compile)[:source]

    case source do
      nil -> nil
      source -> List.to_string(source)
    end
  end

  @doc """
  Prints the current working directory.
  """
  def pwd do
    IO.puts IEx.color(:eval_info, System.cwd!)
  end

  @doc """
  Changes the current working directory to the given path.
  """
  def cd(directory) when is_binary(directory) do
    case File.cd(expand_home(directory)) do
      :ok -> pwd
      {:error, :enoent} ->
        IO.puts IEx.color(:eval_error, "No directory #{directory}")
    end
  end

  @doc """
  Produces a simple list of a directory's contents.

  If `path` points to a file, prints its full path.
  """
  def ls(path \\ ".") when is_binary(path) do
    path = expand_home(path)
    case File.ls(path) do
      {:ok, items} ->
        sorted_items = Enum.sort(items)
        ls_print(path, sorted_items)

      {:error, :enoent} ->
        IO.puts IEx.color(:eval_error, "No such file or directory #{path}")

      {:error, :enotdir} ->
        IO.puts IEx.color(:eval_info, Path.absname(path))
    end
  end

  defp expand_home(<<?~, rest :: binary>>) do
    System.user_home! <> rest
  end

  defp expand_home(other), do: other

  defp ls_print(_, []) do
    :ok
  end

  defp ls_print(path, list) do
    # print items in multiple columns (2 columns in the worst case)
    lengths = Enum.map(list, &String.length(&1))
    maxlen = maxlength(lengths)
    width = min(maxlen, 30) + 5
    ls_print(path, list, width)
  end

  defp ls_print(path, list, width) do
    Enum.reduce(list, 0, fn(item, len) ->
      if len >= 80 do
        IO.puts ""
        len = 0
      end
      IO.write format_item(Path.join(path, item), String.ljust(item, width))
      len+width
    end)
    IO.puts ""
  end

  defp maxlength(list) do
    Enum.reduce(list, 0, &max(&1, &2))
  end

  defp format_item(path, representation) do
    case File.stat(path) do
      {:ok, %File.Stat{type: :device}} ->
        IEx.color(:ls_device, representation)
      {:ok, %File.Stat{type: :directory}} ->
        IEx.color(:ls_directory, representation)
      _ ->
        representation
    end
  end

  @doc """
  Respawns the current shell by starting a new shell process.

  Returns `true` if it worked.
  """
  def respawn do
    if whereis = IEx.Server.whereis do
      send whereis, {:respawn, self}
      dont_display_result
    end
  end

  @doc """
  Evaluates the contents of the file at `path` as if it were directly typed into
  the shell.

  `path` has to be a literal string. `path` is automatically expanded via
  `Path.expand/1`.

  ## Non-existent files

  By default, `import_file/1` fails when the given file does not exist. However,
  since this macro is expanded at compile-time, it's not possible to
  conditionally import a file since the macro is always expanded:

      # This raises a File.Error if ~/.iex.exs doesn't exist.
      if ("~/.iex.exs" |> Path.expand |> File.exists?) do
        import_file "~/.iex.exs"
      end

  This is why an `:optional` option can be passed to `import_file/1`. The
  default value of this option is `false`, meaning that an exception will be
  raised if the given file is missing. If `:optional` is set to `true`, missing
  files will be ignored and `import_file/1` will just compile to `nil`.

  ## Examples

      # ~/file.exs
      value = 13

      # in the shell
      iex(1)> import_file "~/file.exs"
      13
      iex(2)> value
      13
      iex(3)> import_file "nonexisting.file.ex", optional: true
      nil

  """
  defmacro import_file(path, opts \\ [])

  defmacro import_file(path, opts) when is_binary(path) do
    optional? = Keyword.get(opts, :optional, false)
    path = Path.expand(path)

    if not optional? or File.exists?(path) do
      path |> File.read! |> Code.string_to_quoted!(file: path)
    end
  end

  defmacro import_file(_path, _opts) do
    raise ArgumentError, "import_file/1 expects a literal binary as its argument"
  end

  # Compiles and loads an Erlang source file, returns {module, binary}
  defp compile_erlang(source) do
    source = Path.relative_to_cwd(source) |> String.to_char_list
    case :compile.file(source, [:binary, :report]) do
      {:ok, module, binary} ->
        :code.purge(module)
        {:module, module} = :code.load_binary(module, source, binary)
        {module, binary}
      _ ->
        raise CompileError
    end
  end

  defp history, do: Process.get(:iex_history)

  @doc """
  Creates a PID with 3 non negative integers passed as arguments
  to the function.

  ## Examples
      iex> pid(0, 21, 32)
      #PID<0.21.32>
      iex> pid(0, 64, 2048)
      #PID<0.64.2048>
  """
  def pid(x, y, z) when is_integer(x) and x >= 0 and
                        is_integer(y) and y >= 0 and
                        is_integer(z) and z >= 0 do
    :c.pid(x, y, z)
  end
end

defmodule IEx.Helpers do
  @moduledoc """
  Welcome to Interactive Elixir. You are currently
  seeing the documentation for the module `IEx.Helpers`
  which provides many helpers to make Elixir's shell
  more joyful to work with.

  This message was triggered by invoking the helper
  `h()`, usually referred to as `h/0` (since it expects 0
  arguments).

  There are many other helpers available:

    * `b/1`           - prints callbacks info and docs for a given module
    * `c/2`           - compiles a file at the given path
    * `cd/1`          - changes the current directory
    * `clear/0`       - clears the screen
    * `flush/0`       - flushes all messages sent to the shell
    * `h/0`           - prints this help message
    * `h/1`           - prints help for the given module, function or macro
    * `l/1`           - loads the given module's beam code
    * `ls/0`          - lists the contents of the current directory
    * `ls/1`          - lists the contents of the specified directory
    * `pid/3`         - creates a PID with the 3 integer arguments passed
    * `pwd/0`         - prints the current working directory
    * `r/1`           - recompiles and reloads the given module's source file
    * `respawn/0`     - respawns the current shell
    * `s/1`           - prints spec information
    * `t/1`           - prints type information
    * `v/0`           - retrieves the last value from the history
    * `v/1`           - retrieves the nth value from the history
    * `import_file/1` - evaluates the given file in the shell's context

  Help for functions in this module can be consulted
  directly from the command line, as an example, try:

      h(c/2)

  You can also retrieve the documentation for any module
  or function. Try these:

      h(Enum)
      h(Enum.reverse/1)

  To discover all available functions for a module, type the module name
  followed by a dot, then press tab to trigger autocomplete. For example:

      Enum.

  To learn more about IEx as a whole, just type `h(IEx)`.
  """

  import IEx, only: [dont_display_result: 0]

  @doc """
  Compiles the given files.

  It expects a list of files to compile and an optional path to write
  the compiled code to (defaults to the current directory). When compiling
  one file, there is no need to wrap it in a list.

  It returns the name of the compiled modules.

  If you want to recompile an existing module, check `r/1` instead.

  ## Examples

      c ["foo.ex", "bar.ex"], "ebin"
      #=> [Foo, Bar]

      c "baz.ex"
      #=> [Baz]
  """
  def c(files, path \\ ".") when is_binary(path) do
    files = List.wrap(files)

    unless Enum.all?(files, &is_binary/1) do
      raise ArgumentError, "expected a binary or a list of binaries as argument"
    end

    {found, not_found} =
      files
      |> Enum.map(&Path.expand(&1, path))
      |> Enum.partition(&File.exists?/1)

    unless Enum.empty?(not_found) do
      raise ArgumentError, "could not find files #{Enum.join(not_found, ", ")}"
    end

    {erls, exs} = Enum.partition(found, &String.ends_with?(&1, ".erl"))

    modules = Enum.map(erls, fn(source) ->
      {module, binary} = compile_erlang(source)
      base = source |> Path.basename |> Path.rootname
      File.write!(Path.join(path, base <> ".beam"), binary)
      module
    end)

    modules ++ Kernel.ParallelCompiler.files_to_path(exs, path)
  end

  @doc """
  Clears the console screen.

  This function only works if ANSI escape codes are enabled
  on the shell, which means this function is by default
  unavailable on Windows machines.
  """
  def clear do
    if IO.ANSI.enabled? do
      IO.write [IO.ANSI.home, IO.ANSI.clear]
    else
      IO.puts "Cannot clear the screen because ANSI escape codes are not enabled on this shell"
    end
    dont_display_result
  end

  @doc """
  Prints the documentation for `IEx.Helpers`.
  """
  def h() do
    IEx.Introspection.h(IEx.Helpers)
    dont_display_result
  end

  @doc """
  Prints the documentation for the given module
  or for the given function/arity pair.

  ## Examples

      h(Enum)
      #=> Prints documentation for Enum

  It also accepts functions in the format `fun/arity`
  and `module.fun/arity`, for example:

      h receive/1
      h Enum.all?/2
      h Enum.all?

  """
  @h_modules [__MODULE__, Kernel, Kernel.SpecialForms]

  defmacro h(term)
  defmacro h({:/, _, [call, arity]} = term) do
    args =
      case Macro.decompose_call(call) do
        {_mod, :__info__, []} when arity == 1 ->
          [Module, :__info__, 1]
        {mod, fun, []} ->
          [mod, fun, arity]
        {fun, []} ->
          [@h_modules, fun, arity]
        _ ->
          [term]
      end

    quote do
      IEx.Introspection.h(unquote_splicing(args))
    end
  end

  defmacro h(call) do
    args =
      case Macro.decompose_call(call) do
        {_mod, :__info__, []} ->
          [Module, :__info__, 1]
        {mod, fun, []} ->
          [mod, fun]
        {fun, []} ->
          [@h_modules, fun]
        _ ->
          [call]
      end

    quote do
      IEx.Introspection.h(unquote_splicing(args))
    end
  end

  @doc """
  Prints the documentation for the given callback function.

  It also accepts single module argument to list
  all available behaviour callbacks.

  ## Examples

      b(Mix.Task.run/1)
      b(Mix.Task.run)
      b(Dict)

  """
  defmacro b(term)
  defmacro b({:/, _, [{{:., _, [mod, fun]}, _, []}, arity]}) do
    quote do
      IEx.Introspection.b(unquote(mod), unquote(fun), unquote(arity))
    end
  end

  defmacro b({{:., _, [mod, fun]}, _, []}) do
    quote do
      IEx.Introspection.b(unquote(mod), unquote(fun))
    end
  end

  defmacro b(module) do
    quote do
      IEx.Introspection.b(unquote(module))
    end
  end

  @doc """
  Prints the types for the given module or for the given function/arity pair.

  ## Examples

      t(Enum)
      t(Enum.t/0)
      t(Enum.t)
  """
  defmacro t(term)
  defmacro t({:/, _, [{{:., _, [mod, fun]}, _, []}, arity]}) do
    quote do
      IEx.Introspection.t(unquote(mod), unquote(fun), unquote(arity))
    end
  end

  defmacro t({{:., _, [mod, fun]}, _, []}) do
    quote do
      IEx.Introspection.t(unquote(mod), unquote(fun))
    end
  end

  defmacro t(module) do
    quote do
      IEx.Introspection.t(unquote(module))
    end
  end

  @doc """
  Prints the specs for the given module or for the given function/arity pair.

  ## Examples

      s(Enum)
      s(Enum.all?)
      s(Enum.all?/2)
      s(is_atom)
      s(is_atom/1)

  """
  defmacro s(term)
  defmacro s({:/, _, [call, arity]} = term) do
    args =
      case Macro.decompose_call(call) do
        {mod, fun, []} -> [mod, fun, arity]
        {fun, []} -> [Kernel, fun, arity]
        _ -> [term]
      end

    quote do
      IEx.Introspection.s(unquote_splicing(args))
    end
  end

  defmacro s(call) do
    args =
      case Macro.decompose_call(call) do
        {mod, fun, []} -> [mod, fun]
        {fun, []} -> [Kernel, fun]
        _ -> [call]
      end

    quote do
      IEx.Introspection.s(unquote_splicing(args))
    end
  end

  @doc """
  Retrieves the nth expression's value from the history.

  Use negative values to lookup expression values relative to the current one.
  For instance, v(-1) returns the result of the last evaluated expression.
  """
  def v(n \\ -1) do
    IEx.History.nth(history, n) |> elem(2)
  end

  @doc """
  Recompiles and reloads the given `module`.

  Please note that all the modules defined in the same
  file as `module` are recompiled and reloaded.

  ## In-memory reloading

  When we reload the module in IEx, we recompile the module source code,
  updating its contents in memory. The original `.beam` file in disk,
  probably the one where the first definition of the module came from,
  does not change at all.

  Since typespecs and docs are loaded from the .beam file (they are not
  loaded in memory with the module because there is no need for them to
  be in memory), they are not reloaded when you reload the module.
  """
  def r(module) when is_atom(module) do
    {:reloaded, module, do_r(module)}
  end

  defp do_r(module) do
    unless Code.ensure_loaded?(module) do
      raise ArgumentError, "could not load nor find module: #{inspect module}"
    end

    source = source(module)
    cond do
      source == nil ->
        raise ArgumentError, "could not find source for module: #{inspect module}"

      not File.exists?(source) ->
        raise ArgumentError, "could not find source (#{source}) for module: #{inspect module}"

      String.ends_with?(source, ".erl") ->
        [compile_erlang(source) |> elem(0)]

      true ->
        Enum.map(Code.load_file(source), fn {name, _} -> name end)
    end
  end

  @doc """
  Loads the given module's beam code (and ensures any previous
  old version was properly purged before).

  This function is useful when you know the bytecode for module
  has been updated in the filesystem and you want to tell the VM
  to load it.
  """
  def l(module) when is_atom(module) do
    :code.purge(module)
    :code.load_file(module)
  end

  @doc """
  Flushes all messages sent to the shell and prints them out.
  """
  def flush do
    do_flush(IEx.inspect_opts)
  end

  defp do_flush(inspect_opts) do
    receive do
      msg ->
        IO.inspect(msg, inspect_opts)
        do_flush(inspect_opts)
    after
      0 -> :ok
    end
  end

  defp source(module) do
    source = module.module_info(:compile)[:source]

    case source do
      nil -> nil
      source -> List.to_string(source)
    end
  end

  @doc """
  Prints the current working directory.
  """
  def pwd do
    IO.puts IEx.color(:eval_info, System.cwd!)
  end

  @doc """
  Changes the current working directory to the given path.
  """
  def cd(directory) when is_binary(directory) do
    case File.cd(expand_home(directory)) do
      :ok -> pwd
      {:error, :enoent} ->
        IO.puts IEx.color(:eval_error, "No directory #{directory}")
    end
  end

  @doc """
  Produces a simple list of a directory's contents.

  If `path` points to a file, prints its full path.
  """
  def ls(path \\ ".") when is_binary(path) do
    path = expand_home(path)
    case File.ls(path) do
      {:ok, items} ->
        sorted_items = Enum.sort(items)
        ls_print(path, sorted_items)

      {:error, :enoent} ->
        IO.puts IEx.color(:eval_error, "No such file or directory #{path}")

      {:error, :enotdir} ->
        IO.puts IEx.color(:eval_info, Path.absname(path))
    end
  end

  defp expand_home(<<?~, rest :: binary>>) do
    System.user_home! <> rest
  end

  defp expand_home(other), do: other

  defp ls_print(_, []) do
    :ok
  end

  defp ls_print(path, list) do
    # print items in multiple columns (2 columns in the worst case)
    lengths = Enum.map(list, &String.length(&1))
    maxlen = maxlength(lengths)
    width = min(maxlen, 30) + 5
    ls_print(path, list, width)
  end

  defp ls_print(path, list, width) do
    Enum.reduce(list, 0, fn(item, len) ->
      if len >= 80 do
        IO.puts ""
        len = 0
      end
      IO.write format_item(Path.join(path, item), String.ljust(item, width))
      len+width
    end)
    IO.puts ""
  end

  defp maxlength(list) do
    Enum.reduce(list, 0, &max(&1, &2))
  end

  defp format_item(path, representation) do
    case File.stat(path) do
      {:ok, %File.Stat{type: :device}} ->
        IEx.color(:ls_device, representation)
      {:ok, %File.Stat{type: :directory}} ->
        IEx.color(:ls_directory, representation)
      _ ->
        representation
    end
  end

  @doc """
  Respawns the current shell by starting a new shell process.

  Returns `true` if it worked.
  """
  def respawn do
    if whereis = IEx.Server.whereis do
      send whereis, {:respawn, self}
      dont_display_result
    end
  end

  @doc """
  Evaluates the contents of the file at `path` as if it were directly typed into
  the shell.

  `path` has to be a literal string. `path` is automatically expanded via
  `Path.expand/1`.

  ## Non-existent files

  By default, `import_file/1` fails when the given file does not exist. However,
  since this macro is expanded at compile-time, it's not possible to
  conditionally import a file since the macro is always expanded:

      # This raises a File.Error if ~/.iex.exs doesn't exist.
      if ("~/.iex.exs" |> Path.expand |> File.exists?) do
        import_file "~/.iex.exs"
      end

  This is why an `:optional` option can be passed to `import_file/1`. The
  default value of this option is `false`, meaning that an exception will be
  raised if the given file is missing. If `:optional` is set to `true`, missing
  files will be ignored and `import_file/1` will just compile to `nil`.

  ## Examples

      # ~/file.exs
      value = 13

      # in the shell
      iex(1)> import_file "~/file.exs"
      13
      iex(2)> value
      13
      iex(3)> import_file "nonexisting.file.ex", optional: true
      nil

  """
  defmacro import_file(path, opts \\ [])

  defmacro import_file(path, opts) when is_binary(path) do
    optional? = Keyword.get(opts, :optional, false)
    path = Path.expand(path)

    if not optional? or File.exists?(path) do
      path |> File.read! |> Code.string_to_quoted!(file: path)
    end
  end

  defmacro import_file(_path, _opts) do
    raise ArgumentError, "import_file/1 expects a literal binary as its argument"
  end

  # Compiles and loads an Erlang source file, returns {module, binary}
  defp compile_erlang(source) do
    source = Path.relative_to_cwd(source) |> String.to_char_list
    case :compile.file(source, [:binary, :report]) do
      {:ok, module, binary} ->
        :code.purge(module)
        {:module, module} = :code.load_binary(module, source, binary)
        {module, binary}
      _ ->
        raise CompileError
    end
  end

  defp history, do: Process.get(:iex_history)

  @doc """
  Creates a PID with 3 non negative integers passed as arguments
  to the function.

  ## Examples
      iex> pid(0, 21, 32)
      #PID<0.21.32>
      iex> pid(0, 64, 2048)
      #PID<0.64.2048>
  """
  def pid(x, y, z) when is_integer(x) and x >= 0 and
                        is_integer(y) and y >= 0 and
                        is_integer(z) and z >= 0 do
    :c.pid(x, y, z)
  end
end


Simplify IEx.History implementation

Simplify IEx.History implementation

defmodule IEx.History do
  @moduledoc false

  alias IEx.History

  defstruct queue: :queue.new(), size: 0, start: 1

  @doc """
  Initializes IEx history state.
  """
  def init(), do: %History{}

  @doc """
  Appends one entry to the history.
  """
  def append(%History{} = state, entry, limit) do
    {collect?, state} =
      state
      |> append(entry)
      |> prune(limit)

    if collect?, do: collect_garbage()
    state
  end

  @doc """
  Enumerates over all items in the history starting from the oldest one and
  applies `fun` to each one in turn.
  """
  def each(%History{} = state, fun) do
    state
    |> to_list()
    |> Enum.each(fun)
  end

  @doc """
  Gets the nth item from the history.

  If `n` < 0, the count starts from the most recent item and goes back in time.
  """
  # Traverses the queue front-to-back if the index is positive.
  def nth(%History{queue: q, size: size, start: start}, n)
      when n - start >= 0 and n - start < size do
    get_nth(q, n - start)
  end

  # Traverses the queue back-to-front if the index is negative.
  def nth(%History{queue: q, size: size, start: start}, n)
      when n < 0 and size + n >= start - 1 do
    get_nth(:queue.reverse(q), abs(n) - 1)
  end

  def nth(%History{}, n) do
    raise "v(#{n}) is out of bounds"
  end

  defp get_nth(q, 0), do: :queue.head(q)
  defp get_nth(q, n) when n > 0, do: get_nth(:queue.tail(q), n - 1)

  defp append(%{queue: q, size: size} = state, item) do
    %{state | queue: :queue.in(item, q), size: size + 1}
  end

  defp to_list(%{queue: q}), do: :queue.to_list(q)

  # Based on https://github.com/erlang/otp/blob/7dcccee4371477e983f026db9e243cb66900b1ef/lib/stdlib/src/shell.erl#L1401
  defp collect_garbage() do
    collect_proc_garbage(Process.whereis(:user))
    collect_proc_garbage(Process.group_leader())
    :erlang.garbage_collect()
  end

  defp collect_proc_garbage(process) do
    try do
      :erlang.garbage_collect(process)
    catch
      _, _ -> nil
    end
  end

  defp prune(%{start: start} = state, limit) do
    prune(state, start, limit, false)
  end

  defp prune(state, _, limit, _) when limit < 0 do
    {false, state}
  end

  defp prune(%{size: size} = state, counter, limit, collect?) when size - counter < limit do
    {collect?, %{state | start: counter}}
  end

  defp prune(%{queue: q} = state, counter, limit, collect?) do
    {{:value, entry}, q} = :queue.out(q)
    collect? = collect? || has_binary(entry)
    prune(%{state | queue: q}, counter + 1, limit, collect?)
  end

  # Checks val and each of its elements (if it is a list or a tuple)
  # recursively to see if it has any large binaries (outside of the heap).
  defp has_binary(val) do
    try do
      has_bin(val)
    catch
      :throw, :found -> true
    end
  end

  defp has_bin(val) when is_tuple(val), do: has_bin(val, tuple_size(val) - 1)

  defp has_bin([head | tail]) do
    has_bin(head)
    has_bin(tail)
  end

  defp has_bin(val) when byte_size(val) > 64, do: throw(:found)

  defp has_bin(_), do: false

  defp has_bin(_, -1), do: false

  defp has_bin(tuple, index) do
    has_bin(elem(tuple, index))
    has_bin(tuple, index - 1)
  end
end

defmodule IEx.History.State do
  @moduledoc false

  defstruct queue: :queue.new(), size: 0, start: 1

  def append(%{queue: q, size: size} = state, item) do
    %{state | queue: :queue.in(item, q), size: size + 1}
  end

  def to_list(%{queue: q}), do: :queue.to_list(q)

  # Traverses the queue front-to-back if the index is positive.
  def nth(%{queue: q, size: size, start: start}, n)
      when n - start >= 0 and n - start < size,
      do: get_nth(q, n - start)

  # Traverses the queue back-to-front if the index is negative.
  def nth(%{queue: q, size: size, start: start}, n)
      when n < 0 and size + n >= start - 1,
      do: get_nth(:queue.reverse(q), abs(n) - 1)

  def nth(_, _), do: nil

  defp get_nth(q, 0), do: :queue.head(q)
  defp get_nth(q, n) when n > 0, do: get_nth(:queue.tail(q), n - 1)

  # Traverses the queue front-to-back, dropping items as we go
  # until its size is within the specified limit.
  #
  # The "start" value contains the index of the expression at the head
  # of the queue.
  def prune(%{start: start} = state, limit), do: prune(state, start, limit, false)

  defp prune(state, _, limit, _) when limit < 0, do: {false, state}

  defp prune(%{size: size} = state, counter, limit, collect?)
       when size - counter < limit,
       do: {collect?, %{state | start: counter}}

  defp prune(%{queue: q} = state, counter, limit, collect?) do
    {{:value, entry}, q} = :queue.out(q)
    collect? = collect? || has_binary(entry)
    prune(%{state | queue: q}, counter + 1, limit, collect?)
  end

  # Checks val and each of its elements (if it is a list or a tuple)
  # recursively to see if it has any large binaries (outside of the heap).
  defp has_binary(val) do
    try do
      has_bin(val)
    catch
      :throw, :found -> true
    end
  end

  defp has_bin(val) when is_tuple(val), do: has_bin(val, tuple_size(val) - 1)

  defp has_bin([head | tail]) do
    has_bin(head)
    has_bin(tail)
  end

  defp has_bin(val) when byte_size(val) > 64, do: throw(:found)

  defp has_bin(_), do: false

  defp has_bin(_, -1), do: false

  defp has_bin(tuple, index) do
    has_bin(elem(tuple, index))
    has_bin(tuple, index - 1)
  end
end

defmodule IEx.History do
  @moduledoc false

  alias IEx.History.State

  @doc """
  Initializes IEx history state.
  """
  def init(), do: %State{}

  @doc """
  Appends one entry to the history.
  """
  def append(%State{} = state, entry, limit) do
    {collect?, state} =
      State.append(state, entry)
      |> State.prune(limit)

    if collect?, do: collect_garbage()
    state
  end

  @doc """
  Enumerates over all items in the history starting from the oldest one and
  applies `fun` to each one in turn.
  """
  def each(%State{} = state, fun) do
    State.to_list(state)
    |> Enum.each(fun)
  end

  @doc """
  Gets the nth item from the history.

  If `n` < 0, the count starts from the most recent item and goes back in time.
  """
  def nth(%State{} = state, n) do
    case State.nth(state, n) do
      nil -> raise "v(#{n}) is out of bounds"
      entry -> entry
    end
  end

  # Based on https://github.com/erlang/otp/blob/7dcccee4371477e983f026db9e243cb66900b1ef/lib/stdlib/src/shell.erl#L1401
  defp collect_garbage() do
    collect_proc_garbage(Process.whereis(:user))
    collect_proc_garbage(Process.group_leader())
    :erlang.garbage_collect()
  end

  defp collect_proc_garbage(process) do
    try do
      :erlang.garbage_collect(process)
    catch
      _, _ -> nil
    end
  end
end


increase test timouts

increase test timouts

# Copyright 2019 Volvo Cars
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# ”License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# “AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

defmodule GRPCServiceTest do
  use ExUnit.Case, async: false

  @gateway_pid GRPCService.Application.get_gateway_pid()

  @body "BodyCANhs"
  @lin "Lin"

  @tag :success_with_dbc
  test "setup connection" do
    channel = GRPCClientTest.setup_connection()
    assert channel.host == "localhost"
    assert channel.port == 50051
  end

  @tag :success_with_dbc
  test "make grpc call, (OpenPassWindow) make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", :empty}]
    GRPCClientTest.test_open_window()
    assert_receive :cache_decoded
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass_UB"]) == [{"WinSwtReqToPass_UB", 1}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 4}]
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", 0}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make grpc call, (ClosePassWindow) make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", :empty}]
    GRPCClientTest.test_close_window()
    assert_receive :cache_decoded
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass_UB"]) == [{"WinSwtReqToPass_UB", 1}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 2}]
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", 0}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make functional (set_fan_speed) hammer call with one shot, make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 0)
    assert_receive :cache_decoded
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make network hammer call with one shot, make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 0)
    assert_receive :cache_decoded
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make hammer call with 10 hz, make sure it reaches cache and make sure it's stoppable, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 100)
    assert_receive :cache_decoded

    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]

    GRPCClientTest.start_hvac_hammer(12, 0)
    # stop it again
    # once it's stopped write to cache and chech that it's not updated again
    SignalServerProxy.publish(@gateway_pid, [{"HmiHvacFanLvlFrnt", 5}], :none, String.to_atom(@body))
    assert_receive :cache_decoded
    :timer.sleep(100)
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 5}]

    simple_terminate()
  end

  @tag :success_with_dbc
  test "publish raw bytes make sure they arrive as published" do
    simple_initialize()
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")

    <<expected::size(16)>> = <<256::size(16)>>

    spawn(GRPCClientTest, :subscribe_to_signal, [["CCMVFCVectorFrame"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(500)

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CCMVFCVectorFrame"]) == [{"CCMVFCVectorFrame", :empty}]
    source = Base.ClientId.new(id: "publisher_string")
    signal1 = Base.SignalId.new(name: "CCMVFCVectorFrame", namespace: Base.NameSpace.new(name: @body))
    signals_with_payload = [
      Base.Signal.new(id: signal1, raw: <<expected::size(16)>>)
    ]
    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))
    _stream = channel |> Base.NetworkService.Stub.publish_signals(request)
    assert_receive :cache_decoded

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CCMVFCVectorFrame"]) == [{"CCMVFCVectorFrame", expected}]

    :timer.sleep(500)

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}
    assert response.payload == {:integer, expected}
    assert response.raw == <<expected::size(16)>>
    assert response.id.name == "CCMVFCVectorFrame"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "publish signal and read it using readfunction" do
    simple_initialize
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    # check empty...
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", :empty}]

    source = Base.ClientId.new(id: "source_string")
    signal1 = Base.SignalId.new(name: "HmiHvacFanLvlFrnt", namespace: Base.NameSpace.new(name: @body))
    signal2 = Base.SignalId.new(name: "WinSwtReqToPass", namespace: Base.NameSpace.new(name: @body))
    signal3 = Base.SignalId.new(name: "CEMBodyDevFr15", namespace: Base.NameSpace.new(name: @body))
    signal4 = Base.SignalId.new(name: "BfrLin18Fr00", namespace: Base.NameSpace.new(name: @lin))
    signal5 = Base.SignalId.new(name: "WinPosnStsDrv", namespace: Base.NameSpace.new(name: @body))

    signals_with_payload = [
      Base.Signal.new(id: signal1, payload: {:integer, 3}),
      Base.Signal.new(id: signal2, payload: {:double, 0.5}),
      Base.Signal.new(id: signal3, payload: {:double, 7.5}),
      Base.Signal.new(id: signal4, payload: {:arbitration, true}),
      Base.Signal.new(id: signal5, payload: {:integer, 23}, raw: <<01,02>>)
    ] |> Enum.sort

    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))

    # now publish
    stream = channel |> Base.NetworkService.Stub.publish_signals(request)

    assert_receive :cache_decoded

    # let read

    signals = [
      signal1, signal2, signal3, signal4, signal5
    ]

    request = Base.SignalIds.new(signalId: signals)
    response = Base.NetworkService.Stub.read_signals(channel, request)

    {:ok, %Base.Signals{signal: signals}} = response
    [returned_signal | t] = signals |> Enum.sort
    assert returned_signal.payload == {:empty, true}
    assert returned_signal.id.name == "BfrLin18Fr00"
    assert returned_signal.id.namespace.name == @lin
    assert returned_signal.raw == <<>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:double, 7.5}
    assert returned_signal.id.name == "CEMBodyDevFr15"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:integer, 3}
    assert returned_signal.id.name == "HmiHvacFanLvlFrnt"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<3>>

    [returned_signal | t] = t
    assert returned_signal.id.name == "WinPosnStsDrv"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<01,02>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:double, 0.5}
    assert returned_signal.id.name == "WinSwtReqToPass"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<>>

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: @body)
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) > 0)
    [first | rem] = response.frame

    assert ((%Base.SignalInfo{id: %Base.SignalId{name: "DDMBodyFr01", namespace: %Base.NameSpace{name: "BodyCANhs"}}, metaData: %Base.MetaData{description: "", isRaw: false, max: 0, min: 0, size: 64, unit: ""}}) == first.signalInfo)

    [firstkid | rem] = first.childInfo
    assert ((%Base.SignalInfo{id: %Base.SignalId{name: "ChdLockgProtnFailrStsToHmi_UB", namespace: %Base.NameSpace{name: "BodyCANhs"}}, metaData: %Base.MetaData{description: "", isRaw: false, max: 0, min: 0, size: 1, unit: ""}} == firstkid))

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server - return empty list" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: "made_up_namespace")
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) == 0)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server - use virtual network" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: "Virtual")
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) == 0)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "get configuration" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.Empty.new()
    {:ok, response} = Base.SystemService.Stub.get_configuration(channel, request)
    assert (Enum.count(response.networkInfo) == 2)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "write signal and make sure it reaches cache" do
    simple_initialize()
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", :empty}]

    source = Base.ClientId.new(id: "source_string")
    namespace = Base.NameSpace.new(name: @body)
    signal1 = Base.SignalId.new(name: "HmiHvacFanLvlFrnt", namespace: namespace)
    signal2 = Base.SignalId.new(name: "WinSwtReqToPass", namespace: namespace)
    signal3 = Base.SignalId.new(name: "CEMBodyDevFr15", namespace: namespace)
    signal4 = Base.SignalId.new(name: "BfrLin18Fr00", namespace: Base.NameSpace.new(name: @lin))

    signals_with_payload = [
      Base.Signal.new(id: signal1, payload: {:integer, 3}),
      Base.Signal.new(id: signal2, payload: {:double, 0.5}),
      Base.Signal.new(id: signal3, payload: {:double, 7.5}),
      Base.Signal.new(id: signal4, payload: {:arbitration, true})
    ]
    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))

    stream = channel |> Base.NetworkService.Stub.publish_signals(request)

    assert_receive :cache_decoded

    :timer.sleep(500)

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 3}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 0.5}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CEMBodyDevFr15"]) == [{"CEMBodyDevFr15", 7.5}]
    # arbitration doesn't reach cache....
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["BfrLin18Fr00"]) == [{"BfrLin18Fr00", :empty}]

    simple_terminate()
  end

  require Logger

  @tag :success_with_dbc
  test "subscribe to signal and make sure it arrives from can, cache -> grpc" do
    simple_initialize()

    field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass", "MirrFoldStsAtDrvr"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(500)
    Payload.Signal.handle_raw_can_frames(
      Payload.Name.generate_name_from_namespace(String.to_atom(@body), :signal), :test_source,
      [{field.id, <<0x12, 0x34,0x56,0x78,0xab,0xcd,0xef,0x01>>}])

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: signals}}}

    [response | t] = signals |> Enum.sort()
    assert response.payload == {:integer, 7}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    [response | t] = t
    assert response.payload == {:integer, 1}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "subscribe to signal and make sure it arrives from signal broker" do
    simple_initialize()

    # field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(2000)
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    # second signal, same as before
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :this1
  test "subscribe to signal and make sure it arrives from signal broker onchange active" do
     simple_initialize()

    # field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass", "MirrFoldStsAtDrvr"], @body, GRPCClientTest.setup_connection(), true])

    :timer.sleep(2000)
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    # second signal, same as before, only the changed should arrive
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}, {"MirrFoldStsAtDrvr", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 3}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 6}, {"MirrFoldStsAtDrvr", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 6}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 1}, {"MirrFoldStsAtDrvr", 2}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: signals}}}, 1000

    [response | t] = signals |> Enum.sort()
    assert response.payload == {:integer, 2}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    [response | t] = t
    assert response.payload == {:integer, 1}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "subscribe to fan speed" do
    simple_initialize()
    spawn(GRPCClientTest, :subscribe_to_fan_speed, ["source_string", GRPCClientTest.setup_connection()])
    :timer.sleep(500)
    SignalServerProxy.publish(@gateway_pid, [{"HmiHvacFanLvlFrnt", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, response}}
    assert response.payload == 3

    simple_terminate()
  end

  defp start_diag_flow() do

  end

  defmodule LocalListener do
    use GenServer
    require Logger
    alias SignalBase.Message
    @gateway_pid GRPCService.Application.get_gateway_pid()
    @body "BodyCANhs"

    defstruct [
      test_pid: nil
    ]

    def start_link(name, {signal, test_pid}) do
      GenServer.start_link(__MODULE__, {name, signal, test_pid}, name: name)
    end

    #Server
    def init({name, signal, test_pid}) do
      SignalServerProxy.register_listeners(@gateway_pid, [signal], :sniffer, self(), String.to_atom(@body))
      {:ok, %{test_pid: test_pid}}
    end

    def handle_cast({:signal, %Message{name_values: channels_with_values, time_stamp: timestamp, namespace: namespace}}, state) do
      [{name, value}] = channels_with_values
      send(state.test_pid, value)
      {:noreply, state}
    end
  end

  def local_publisher(response_data_raw) do
    # send messages back to Diagnostics ans make sure the a re concatenaded accordingly
    :timer.sleep(500)
    Enum.each(response_data_raw, fn(entry) ->
      SignalServerProxy.publish(@gateway_pid, [{"TesterPhysicalResCEMHS", entry}], :none, String.to_atom(@body))
    end)
  end

  @expected_request 0x0322F19000000000
  @expected_request2 0x3000000000000000
  @tag :success
  test "simple read diagnostics VIN" do

    simple_initialize()

    pid = LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xf190::size(16)>>

    # response_data = [<<16, 27, 98, 241, 144, 89, 86, 49>>, "!FW41L0G", "\"1285060", <<35, 0, 0, 0, 0, 0, 0, 0>>]
    response_data_raw = [0x101B62F190595631, 0x21465734314C3047, 0x2231323835303630, 0x2300000000000000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322F19000000000, 1000
    assert_receive 0x3000000000000000, 1000
    assert response.raw == <<89, 86, 49, 70, 87, 52, 49, 76, 48, 71, 49, 50, 56, 53, 48, 54, 48, 0, 0, 0, 0, 0, 0, 0>>
    simple_terminate()
  end

  @expected_request3 0x0322F12E00000000
  @expected_request4 0x3000000000000000
  @tag :success
  test "simple read diagnostics something else" do

    simple_initialize()

    LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xf12e::size(16)>>

    response_data_raw = [0x102062F12E043322, 0x2119204124153222, 0x2211802404114222, 0x2313208220423165, 0x2482134523200000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322F12E00000000, 1000
    assert_receive 0x3000000000000000, 1000
    assert response.raw == <<4, 51, 34, 25, 32, 65, 36, 21, 50, 34, 17, 128, 36, 4, 17, 66, 34, 19, 32, 130, 32, 66, 49, 101, 130, 19, 69, 35, 32>>

    simple_terminate()
  end

  @tag :success
  test "simple read diagnostics single frame" do

    simple_initialize()

    LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xd11c::size(16)>>

    response_data_raw = [0x462D11C42000000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322D11C00000000, 1000
    # assert_receive 0x3000000000000000, 1000
    # assert_receive :ok, 1000
    assert response.raw == <<66>>

    simple_terminate()
  end

  @simple_conf %{
    BodyCANhs: %{signal_base_pid: :broker0_pid, signal_cache_pid: Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), type: "udp"},
    Virtual: %{signal_base_pid: :broker1_pid, signal_cache_pid: :cache1, type: "virtual"},
  }

  # defp simple_initialize() do
  #   # Application.ensure_all_started(:grpc_service,  :permanent)
  #   {:ok, _} = Util.Forwarder.start_link(self())

  require Logger

  defp simple_initialize() do
    # Application.ensure_all_started(:grpc_service,  :permanent)
    {:ok, pid} = Util.Forwarder.start_link(self())

    {:ok, pid} = SignalServerProxy.start_link({@gateway_pid, @simple_conf, String.to_atom(@body)})
    assert_receive :signal_proxy_ready, 5000
    {:ok, pid} = SignalBase.start_link(:broker0_pid, String.to_atom(@body), nil)
    assert_receive :signal_base_ready, 5000
    # {:ok, _} = SignalBase.start_link(:broker1_pid, :any, nil)
    # {:ok, _} = SignalBase.start_link(:broker2_pid, :any, nil)

    #   def start_link({{device, desc, conn, signal, canwriter, cache, signalbase, namespace, type}, physical}) when is_atom(namespace)
    # {:ok, pid} = AppNgCan.start_link({{"vcan0", Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), :conn, Payload.Name.generate_name_from_namespace(String.to_atom(@body), :signal), :writer, Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), :broker0_pid, String.to_atom(@body), "can"}, dbc_file: "../../configuration/can_files/SPA0610/SPA0610_140404_BodyCANhs.dbc"})

    #   def start_link({namespace, signalbase_pid, conf, server_port, target_host, target_port, type}) when is_atom(namespace) do

    {:ok, pid} = CanUdp.App.start_link({String.to_atom(@body), :broker0_pid, [dbc_file: "../../configuration/can/test.dbc"], 2001, '127.0.0.1', 2000, "udp"})
    assert_receive {:ready_descriptors, :broker0_pid}, 3_000
  end

  defp simple_terminate() do
    # Application.stop(:grpc_service)
    close_processes([@gateway_pid, :broker0_pid])
    close_processes([(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :supervisor))])

    # assert GenServer.stop(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :supervisor)) == :ok

    assert Util.Forwarder.terminate() == :ok
  end

  defp assert_down(pid) do
    ref = Process.monitor(pid)
    assert_receive {:DOWN, ^ref, _, _, _}
  end

  defp close_process(p) do
    :ok = GenServer.stop(p, :normal)
    assert_down(p)
  end

  defp close_processes(pids), do: pids |> Enum.map(&close_process/1)

end

# Copyright 2019 Volvo Cars
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# ”License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# “AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

defmodule GRPCServiceTest do
  use ExUnit.Case, async: false

  @gateway_pid GRPCService.Application.get_gateway_pid()

  @body "BodyCANhs"
  @lin "Lin"

  @tag :success_with_dbc
  test "setup connection" do
    channel = GRPCClientTest.setup_connection()
    assert channel.host == "localhost"
    assert channel.port == 50051
  end

  @tag :success_with_dbc
  test "make grpc call, (OpenPassWindow) make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", :empty}]
    GRPCClientTest.test_open_window()
    assert_receive :cache_decoded
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass_UB"]) == [{"WinSwtReqToPass_UB", 1}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 4}]
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", 0}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make grpc call, (ClosePassWindow) make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", :empty}]
    GRPCClientTest.test_close_window()
    assert_receive :cache_decoded
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass_UB"]) == [{"WinSwtReqToPass_UB", 1}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 2}]
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["DDMBodyFr01"]) == [{"DDMBodyFr01", 0}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make functional (set_fan_speed) hammer call with one shot, make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 0)
    assert_receive :cache_decoded
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make network hammer call with one shot, make sure it reaches cache, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 0)
    assert_receive :cache_decoded
    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]
    simple_terminate()
  end

  @tag :success_with_dbc
  test "make hammer call with 10 hz, make sure it reaches cache and make sure it's stoppable, grpc -> cache" do
    simple_initialize()

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    GRPCClientTest.start_hvac_hammer(12, 100)
    assert_receive :cache_decoded

    # TODO aren't we expecting the raw frame here?
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 12}]

    GRPCClientTest.start_hvac_hammer(12, 0)
    # stop it again
    # once it's stopped write to cache and chech that it's not updated again
    SignalServerProxy.publish(@gateway_pid, [{"HmiHvacFanLvlFrnt", 5}], :none, String.to_atom(@body))
    assert_receive :cache_decoded
    :timer.sleep(100)
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 5}]

    simple_terminate()
  end

  @tag :success_with_dbc
  test "publish raw bytes make sure they arrive as published" do
    simple_initialize()
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")

    <<expected::size(16)>> = <<256::size(16)>>

    spawn(GRPCClientTest, :subscribe_to_signal, [["CCMVFCVectorFrame"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(500)

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CCMVFCVectorFrame"]) == [{"CCMVFCVectorFrame", :empty}]
    source = Base.ClientId.new(id: "publisher_string")
    signal1 = Base.SignalId.new(name: "CCMVFCVectorFrame", namespace: Base.NameSpace.new(name: @body))
    signals_with_payload = [
      Base.Signal.new(id: signal1, raw: <<expected::size(16)>>)
    ]
    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))
    _stream = channel |> Base.NetworkService.Stub.publish_signals(request)
    assert_receive :cache_decoded

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CCMVFCVectorFrame"]) == [{"CCMVFCVectorFrame", expected}]

    :timer.sleep(500)

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}
    assert response.payload == {:integer, expected}
    assert response.raw == <<expected::size(16)>>
    assert response.id.name == "CCMVFCVectorFrame"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "publish signal and read it using readfunction" do
    simple_initialize
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    # check empty...
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", :empty}]

    source = Base.ClientId.new(id: "source_string")
    signal1 = Base.SignalId.new(name: "HmiHvacFanLvlFrnt", namespace: Base.NameSpace.new(name: @body))
    signal2 = Base.SignalId.new(name: "WinSwtReqToPass", namespace: Base.NameSpace.new(name: @body))
    signal3 = Base.SignalId.new(name: "CEMBodyDevFr15", namespace: Base.NameSpace.new(name: @body))
    signal4 = Base.SignalId.new(name: "BfrLin18Fr00", namespace: Base.NameSpace.new(name: @lin))
    signal5 = Base.SignalId.new(name: "WinPosnStsDrv", namespace: Base.NameSpace.new(name: @body))

    signals_with_payload = [
      Base.Signal.new(id: signal1, payload: {:integer, 3}),
      Base.Signal.new(id: signal2, payload: {:double, 0.5}),
      Base.Signal.new(id: signal3, payload: {:double, 7.5}),
      Base.Signal.new(id: signal4, payload: {:arbitration, true}),
      Base.Signal.new(id: signal5, payload: {:integer, 23}, raw: <<01,02>>)
    ] |> Enum.sort

    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))

    # now publish
    stream = channel |> Base.NetworkService.Stub.publish_signals(request)

    assert_receive :cache_decoded

    # let read

    signals = [
      signal1, signal2, signal3, signal4, signal5
    ]

    request = Base.SignalIds.new(signalId: signals)
    response = Base.NetworkService.Stub.read_signals(channel, request)

    {:ok, %Base.Signals{signal: signals}} = response
    [returned_signal | t] = signals |> Enum.sort
    assert returned_signal.payload == {:empty, true}
    assert returned_signal.id.name == "BfrLin18Fr00"
    assert returned_signal.id.namespace.name == @lin
    assert returned_signal.raw == <<>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:double, 7.5}
    assert returned_signal.id.name == "CEMBodyDevFr15"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:integer, 3}
    assert returned_signal.id.name == "HmiHvacFanLvlFrnt"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<3>>

    [returned_signal | t] = t
    assert returned_signal.id.name == "WinPosnStsDrv"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<01,02>>

    [returned_signal | t] = t
    assert returned_signal.payload == {:double, 0.5}
    assert returned_signal.id.name == "WinSwtReqToPass"
    assert returned_signal.id.namespace.name == @body
    assert returned_signal.raw == <<>>

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: @body)
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) > 0)
    [first | rem] = response.frame

    assert ((%Base.SignalInfo{id: %Base.SignalId{name: "DDMBodyFr01", namespace: %Base.NameSpace{name: "BodyCANhs"}}, metaData: %Base.MetaData{description: "", isRaw: false, max: 0, min: 0, size: 64, unit: ""}}) == first.signalInfo)

    [firstkid | rem] = first.childInfo
    assert ((%Base.SignalInfo{id: %Base.SignalId{name: "ChdLockgProtnFailrStsToHmi_UB", namespace: %Base.NameSpace{name: "BodyCANhs"}}, metaData: %Base.MetaData{description: "", isRaw: false, max: 0, min: 0, size: 1, unit: ""}} == firstkid))

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server - return empty list" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: "made_up_namespace")
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) == 0)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "fetch signals from server - use virtual network" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.NameSpace.new(name: "Virtual")
    {:ok, response} = Base.SystemService.Stub.list_signals(channel, request)
    assert (Enum.count(response.frame) == 0)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "get configuration" do
    simple_initialize()

    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    request = Base.Empty.new()
    {:ok, response} = Base.SystemService.Stub.get_configuration(channel, request)
    assert (Enum.count(response.networkInfo) == 2)

    simple_terminate()
  end

  @tag :success_with_dbc
  test "write signal and make sure it reaches cache" do
    simple_initialize()
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", :empty}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", :empty}]

    source = Base.ClientId.new(id: "source_string")
    namespace = Base.NameSpace.new(name: @body)
    signal1 = Base.SignalId.new(name: "HmiHvacFanLvlFrnt", namespace: namespace)
    signal2 = Base.SignalId.new(name: "WinSwtReqToPass", namespace: namespace)
    signal3 = Base.SignalId.new(name: "CEMBodyDevFr15", namespace: namespace)
    signal4 = Base.SignalId.new(name: "BfrLin18Fr00", namespace: Base.NameSpace.new(name: @lin))

    signals_with_payload = [
      Base.Signal.new(id: signal1, payload: {:integer, 3}),
      Base.Signal.new(id: signal2, payload: {:double, 0.5}),
      Base.Signal.new(id: signal3, payload: {:double, 7.5}),
      Base.Signal.new(id: signal4, payload: {:arbitration, true})
    ]
    request = Base.PublisherConfig.new(clientId: source, frequency: 0, signals: Base.Signals.new(signal: signals_with_payload))

    stream = channel |> Base.NetworkService.Stub.publish_signals(request)

    assert_receive :cache_decoded

    :timer.sleep(500)

    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["HmiHvacFanLvlFrnt"]) == [{"HmiHvacFanLvlFrnt", 3}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["WinSwtReqToPass"]) == [{"WinSwtReqToPass", 0.5}]
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["CEMBodyDevFr15"]) == [{"CEMBodyDevFr15", 7.5}]
    # arbitration doesn't reach cache....
    assert Payload.Cache.read_channels(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), ["BfrLin18Fr00"]) == [{"BfrLin18Fr00", :empty}]

    simple_terminate()
  end

  require Logger

  @tag :success_with_dbc
  test "subscribe to signal and make sure it arrives from can, cache -> grpc" do
    simple_initialize()

    field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass", "MirrFoldStsAtDrvr"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(500)
    Payload.Signal.handle_raw_can_frames(
      Payload.Name.generate_name_from_namespace(String.to_atom(@body), :signal), :test_source,
      [{field.id, <<0x12, 0x34,0x56,0x78,0xab,0xcd,0xef,0x01>>}])

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: signals}}}

    [response | t] = signals |> Enum.sort()
    assert response.payload == {:integer, 7}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    [response | t] = t
    assert response.payload == {:integer, 1}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "subscribe to signal and make sure it arrives from signal broker" do
    simple_initialize()

    # field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass"], @body, GRPCClientTest.setup_connection()])

    :timer.sleep(2000)
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    # second signal, same as before
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :this1
  test "subscribe to signal and make sure it arrives from signal broker onchange active" do
     simple_initialize()

    # field = Payload.Descriptions.get_field_by_name(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), "WinSwtReqToPass")
    spawn(GRPCClientTest, :subscribe_to_signal, [["WinSwtReqToPass", "MirrFoldStsAtDrvr"], @body, GRPCClientTest.setup_connection(), true])

    :timer.sleep(2000)
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 5}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    # second signal, same as before, only the changed should arrive
    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 5}, {"MirrFoldStsAtDrvr", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 3}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 6}, {"MirrFoldStsAtDrvr", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: [response | t]}}}, 1000
    assert response.payload == {:integer, 6}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    SignalServerProxy.publish(@gateway_pid, [{"WinSwtReqToPass", 1}, {"MirrFoldStsAtDrvr", 2}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, %Base.Signals{signal: signals}}}, 1000

    [response | t] = signals |> Enum.sort()
    assert response.payload == {:integer, 2}
    assert response.id.name == "MirrFoldStsAtDrvr"
    assert response.id.namespace.name == @body

    [response | t] = t
    assert response.payload == {:integer, 1}
    assert response.id.name == "WinSwtReqToPass"
    assert response.id.namespace.name == @body

    simple_terminate()
  end

  @tag :success_with_dbc
  test "subscribe to fan speed" do
    simple_initialize()
    spawn(GRPCClientTest, :subscribe_to_fan_speed, ["source_string", GRPCClientTest.setup_connection()])
    :timer.sleep(500)
    SignalServerProxy.publish(@gateway_pid, [{"HmiHvacFanLvlFrnt", 3}], :none, String.to_atom(@body))

    assert_receive {:subscription_received, {:ok, response}}
    assert response.payload == 3

    simple_terminate()
  end

  defp start_diag_flow() do

  end

  defmodule LocalListener do
    use GenServer
    require Logger
    alias SignalBase.Message
    @gateway_pid GRPCService.Application.get_gateway_pid()
    @body "BodyCANhs"

    defstruct [
      test_pid: nil
    ]

    def start_link(name, {signal, test_pid}) do
      GenServer.start_link(__MODULE__, {name, signal, test_pid}, name: name)
    end

    #Server
    def init({name, signal, test_pid}) do
      SignalServerProxy.register_listeners(@gateway_pid, [signal], :sniffer, self(), String.to_atom(@body))
      {:ok, %{test_pid: test_pid}}
    end

    def handle_cast({:signal, %Message{name_values: channels_with_values, time_stamp: timestamp, namespace: namespace}}, state) do
      [{name, value}] = channels_with_values
      send(state.test_pid, value)
      {:noreply, state}
    end
  end

  def local_publisher(response_data_raw) do
    # send messages back to Diagnostics ans make sure the a re concatenaded accordingly
    :timer.sleep(500)
    Enum.each(response_data_raw, fn(entry) ->
      SignalServerProxy.publish(@gateway_pid, [{"TesterPhysicalResCEMHS", entry}], :none, String.to_atom(@body))
    end)
  end

  @expected_request 0x0322F19000000000
  @expected_request2 0x3000000000000000
  @tag :success
  test "simple read diagnostics VIN" do

    simple_initialize()

    pid = LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xf190::size(16)>>

    # response_data = [<<16, 27, 98, 241, 144, 89, 86, 49>>, "!FW41L0G", "\"1285060", <<35, 0, 0, 0, 0, 0, 0, 0>>]
    response_data_raw = [0x101B62F190595631, 0x21465734314C3047, 0x2231323835303630, 0x2300000000000000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322F19000000000, 1000
    assert_receive 0x3000000000000000, 1000
    assert response.raw == <<89, 86, 49, 70, 87, 52, 49, 76, 48, 71, 49, 50, 56, 53, 48, 54, 48, 0, 0, 0, 0, 0, 0, 0>>
    simple_terminate()
  end

  @expected_request3 0x0322F12E00000000
  @expected_request4 0x3000000000000000
  @tag :success
  test "simple read diagnostics something else" do

    simple_initialize()

    LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xf12e::size(16)>>

    response_data_raw = [0x102062F12E043322, 0x2119204124153222, 0x2211802404114222, 0x2313208220423165, 0x2482134523200000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322F12E00000000, 1000
    assert_receive 0x3000000000000000, 1000
    assert response.raw == <<4, 51, 34, 25, 32, 65, 36, 21, 50, 34, 17, 128, 36, 4, 17, 66, 34, 19, 32, 130, 32, 66, 49, 101, 130, 19, 69, 35, 32>>

    simple_terminate()
  end

  @tag :success
  test "simple read diagnostics single frame" do

    simple_initialize()

    LocalListener.start_link(:some_name, {"TesterPhysicalReqCEMHS", self()})

    Util.Application.start(1,2)
    Diagnostics.Application.start(1, 2)
    {:ok, channel} = GRPC.Stub.connect("localhost:50051")
    up_link = Base.SignalId.new(name: "TesterPhysicalReqCEMHS", namespace: Base.NameSpace.new(name: @body))
    down_link = Base.SignalId.new(name: "TesterPhysicalResCEMHS", namespace: Base.NameSpace.new(name: @body))
    service_id = <<0x22>>
    data_identifier = <<0xd11c::size(16)>>

    response_data_raw = [0x462D11C42000000]
    spawn(__MODULE__, :local_publisher, [response_data_raw])

    request = Base.DiagnosticsRequest.new(upLink: up_link, downLink: down_link, serviceId: service_id, dataIdentifier: data_identifier)
    {:ok, response} = Base.DiagnosticsService.Stub.send_diagnostics_query(channel, request)

    assert_receive 0x0322D11C00000000, 1000
    # assert_receive 0x3000000000000000, 1000
    # assert_receive :ok, 1000
    assert response.raw == <<66>>

    simple_terminate()
  end

  @simple_conf %{
    BodyCANhs: %{signal_base_pid: :broker0_pid, signal_cache_pid: Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), type: "udp"},
    Virtual: %{signal_base_pid: :broker1_pid, signal_cache_pid: :cache1, type: "virtual"},
  }

  # defp simple_initialize() do
  #   # Application.ensure_all_started(:grpc_service,  :permanent)
  #   {:ok, _} = Util.Forwarder.start_link(self())

  require Logger

  defp simple_initialize() do
    # Application.ensure_all_started(:grpc_service,  :permanent)
    {:ok, pid} = Util.Forwarder.start_link(self())

    {:ok, pid} = SignalServerProxy.start_link({@gateway_pid, @simple_conf, String.to_atom(@body)})
    assert_receive :signal_proxy_ready, 500
    {:ok, pid} = SignalBase.start_link(:broker0_pid, String.to_atom(@body), nil)
    assert_receive :signal_base_ready, 500
    # {:ok, _} = SignalBase.start_link(:broker1_pid, :any, nil)
    # {:ok, _} = SignalBase.start_link(:broker2_pid, :any, nil)

    #   def start_link({{device, desc, conn, signal, canwriter, cache, signalbase, namespace, type}, physical}) when is_atom(namespace)
    # {:ok, pid} = AppNgCan.start_link({{"vcan0", Payload.Name.generate_name_from_namespace(String.to_atom(@body), :desc), :conn, Payload.Name.generate_name_from_namespace(String.to_atom(@body), :signal), :writer, Payload.Name.generate_name_from_namespace(String.to_atom(@body), :cache), :broker0_pid, String.to_atom(@body), "can"}, dbc_file: "../../configuration/can_files/SPA0610/SPA0610_140404_BodyCANhs.dbc"})

    #   def start_link({namespace, signalbase_pid, conf, server_port, target_host, target_port, type}) when is_atom(namespace) do

    {:ok, pid} = CanUdp.App.start_link({String.to_atom(@body), :broker0_pid, [dbc_file: "../../configuration/can/test.dbc"], 2001, '127.0.0.1', 2000, "udp"})
    assert_receive {:ready_descriptors, :broker0_pid}, 3_000
  end

  defp simple_terminate() do
    # Application.stop(:grpc_service)
    close_processes([@gateway_pid, :broker0_pid])
    close_processes([(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :supervisor))])

    # assert GenServer.stop(Payload.Name.generate_name_from_namespace(String.to_atom(@body), :supervisor)) == :ok

    assert Util.Forwarder.terminate() == :ok
  end

  defp assert_down(pid) do
    ref = Process.monitor(pid)
    assert_receive {:DOWN, ^ref, _, _, _}
  end

  defp close_process(p) do
    :ok = GenServer.stop(p, :normal)
    assert_down(p)
  end

  defp close_processes(pids), do: pids |> Enum.map(&close_process/1)

end