Tests for AVRDude / MuonTrap usage

pull/1106/head
Rick Carlino 2020-01-08 10:37:53 -06:00
parent 013d617538
commit 57cc8a8b37
9 changed files with 111 additions and 19 deletions

View File

@ -3,11 +3,12 @@ defmodule FarmbotCore.FarmwareRuntime do
Handles execution of Farmware plugins.
"""
alias Avrdude.MuonTrapAdapter
alias FarmbotCeleryScript.AST
alias FarmbotCore.FarmwareRuntime.PipeWorker
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
alias FarmbotCore.Asset.FarmwareInstallation.Manifest
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
alias FarmbotCore.BotState.FileSystem
alias FarmbotCore.FarmwareRuntime.PipeWorker
alias FarmbotCore.Project
import FarmwareInstallation, only: [install_dir: 1]
@ -67,7 +68,8 @@ defmodule FarmbotCore.FarmwareRuntime do
@doc "Stop a farmware"
def stop(pid) do
Logger.info "Terminating farmware process"
Logger.info("Terminating farmware process")
if Process.alive?(pid) do
GenServer.stop(pid, :normal)
end
@ -75,7 +77,7 @@ defmodule FarmbotCore.FarmwareRuntime do
def init([manifest, env, caller]) do
package = manifest.package
<<clause1 :: binary-size(8), _::binary>> = Ecto.UUID.generate()
<<clause1::binary-size(8), _::binary>> = Ecto.UUID.generate()
request_pipe =
Path.join([
@ -109,8 +111,10 @@ defmodule FarmbotCore.FarmwareRuntime do
)
# Start the plugin.
Logger.debug "spawning farmware: #{exec} #{manifest.args}"
{cmd, _} = spawn_monitor(MuonTrap, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
Logger.debug("spawning farmware: #{exec} #{manifest.args}")
{cmd, _} =
spawn_monitor(MuonTrapAdapter, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
state = %State{
caller: caller,
@ -125,7 +129,7 @@ defmodule FarmbotCore.FarmwareRuntime do
response_pipe_handle: resp
}
send self(), :timeout
send(self(), :timeout)
{:ok, state}
end
@ -142,12 +146,12 @@ defmodule FarmbotCore.FarmwareRuntime do
end
def handle_info(msg, %{context: :error} = state) do
Logger.warn "unhandled message in error state: #{inspect(msg)}"
Logger.warn("unhandled message in error state: #{inspect(msg)}")
{:noreply, state}
end
def handle_info({:step_complete, ref, {:error, reason}}, %{scheduler_ref: ref} = state) do
send state.caller, {:error, reason}
send(state.caller, {:error, reason})
{:noreply, %{state | ref: nil, context: :error}}
end
@ -159,7 +163,7 @@ defmodule FarmbotCore.FarmwareRuntime do
_reply = PipeWorker.write(state.response_pipe_handle, ipc)
# Make sure to `timeout` after this one to go back to the
# get_header context. This will cause another rpc to be processed.
send self(), :timeout
send(self(), :timeout)
{:noreply, %{state | rpc: nil, context: :get_header}}
end
@ -174,14 +178,14 @@ defmodule FarmbotCore.FarmwareRuntime do
# didn't pick up the scheduled AST in a reasonable amount of time.
def handle_info(:timeout, %{context: :process_request} = state) do
Logger.error("Timeout waiting for #{inspect(state.rpc)} to be processed")
send state.caller, {:error, :rpc_timeout}
send(state.caller, {:error, :rpc_timeout})
{:noreply, %{state | context: :error}}
end
# farmware exit
def handle_info({:DOWN, _ref, :process, _pid, _reason}, %{cmd: _cmd_pid} = state) do
Logger.debug("Farmware exit")
send state.caller, {:error, :farmware_exit}
send(state.caller, {:error, :farmware_exit})
{:noreply, %{state | context: :error}}
end
@ -200,14 +204,14 @@ defmodule FarmbotCore.FarmwareRuntime do
# error result of an io:read/2 in :get_header context
def handle_info({PipeWorker, _ref, {:ok, data}}, %{context: :get_header} = state) do
Logger.error("Bad header: #{inspect(data, base: :hex, limit: :infinity)}")
send state.caller, {:error, {:unhandled_packet, data}}
send(state.caller, {:error, {:unhandled_packet, data}})
{:noreply, %{state | context: :error}}
end
# error result of an io:read/2 in :get_header context
def handle_info({PipeWorker, _ref, error}, %{context: :get_header} = state) do
Logger.error("Bad header: #{inspect(error)}")
send state.caller, {:error, :bad_packet_header}
send(state.caller, {:error, :bad_packet_header})
{:noreply, %{state | context: :error}}
end
@ -219,7 +223,7 @@ defmodule FarmbotCore.FarmwareRuntime do
# error result of an io:read/2 in :get_header context
def handle_info({PipeWorker, _ref, error}, %{context: :get_payload} = state) do
Logger.error("Bad payload: #{inspect(error)}")
send state.caller, {:error, :bad_packet_payload}
send(state.caller, {:error, :bad_packet_payload})
{:noreply, %{state | context: :error}}
end
@ -247,10 +251,12 @@ defmodule FarmbotCore.FarmwareRuntime do
Logger.debug("executing rpc from farmware: #{inspect(rpc)}")
# todo(connor) replace this with StepRunner?
FarmbotCeleryScript.execute(rpc, ref)
{:noreply, %{state | rpc: rpc, scheduler_ref: ref, context: :process_request}, @error_timeout_ms}
{:noreply, %{state | rpc: rpc, scheduler_ref: ref, context: :process_request},
@error_timeout_ms}
else
{:error, reason} ->
send state.caller, {:error, reason}
send(state.caller, {:error, reason})
{:noreply, %{state | context: :error}}
end
end
@ -300,6 +306,7 @@ defmodule FarmbotCore.FarmwareRuntime do
header =
<<@packet_header_token::size(16)>> <>
:binary.copy(<<0x00>>, 4) <> <<byte_size(payload)::big-size(32)>>
header <> payload
end
end

View File

@ -39,3 +39,4 @@ config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
firmware_flash_attempt_threshold: 0
config :plug, :validate_header_keys_during_test, true
config :farmbot, :muon_trap_adapter, Avrdude.MuonTrapTestAdapter

View File

@ -30,7 +30,11 @@ defmodule Avrdude do
# call the function for resetting the line before executing avrdude.
call_reset_fun(reset_fun)
MuonTrap.cmd("avrdude", args, into: IO.stream(:stdio, :line), stderr_to_stdout: true)
Avrdude.MuonTrapAdapter.cmd("avrdude", args,
into: IO.stream(:stdio, :line),
stderr_to_stdout: true
)
end
def call_reset_fun(reset_fun) do
@ -39,7 +43,7 @@ defmodule Avrdude do
catch
error_type, error ->
FarmbotCore.Logger.error(1, """
Error calling reset function: #{inspect(reset_fun)}
Error calling reset function: #{inspect(reset_fun)}
error type: #{error_type}
error: #{inspect(error)}
""")

View File

@ -0,0 +1,17 @@
defmodule Avrdude.MuonTrapAdapter do
@type exe :: String.t()
@type args :: [String.t()]
@type io_stream :: Enumerable.t()
@type option :: {:into, io_stream()} | {:stderr_to_stdout, boolean()}
@callback cmd(exe, args, list(option)) :: {String.t(), non_neg_integer}
@doc false
def adapter do
Application.get_env(:farmbot, :muon_trap_adapter, Avrdude.MuonTrapDefaultAdapter)
end
def cmd(exe, args, options) do
adapter().cmd(exe, args, options)
end
end

View File

@ -0,0 +1,6 @@
defmodule Avrdude.MuonTrapDefaultAdapter do
@behaviour Avrdude.MuonTrapAdapter
@impl Avrdude.MuonTrapAdapter
defdelegate cmd(exe, args, options), to: MuonTrap, as: :cmd
end

View File

@ -0,0 +1,8 @@
defmodule Avrdude.MuonTrapTestAdapter do
@behaviour Avrdude.MuonTrapAdapter
@impl Avrdude.MuonTrapAdapter
def cmd(exe, args, options) do
{exe, args, options}
end
end

View File

@ -0,0 +1,40 @@
Mox.defmock(Avrdude.MuonTrapAdapter, for: Avrdude.MuonTrapAdapter)
defmodule FarmbotOs.AvrdudeTest do
use ExUnit.Case
import Mox
setup [:verify_on_exit!]
test "works" do
File.touch("/tmp/wow")
expect(Avrdude.MuonTrapAdapter, :cmd, fn cmd, args, opts ->
assert cmd == "avrdude"
assert args == [
"-patmega2560",
"-cwiring",
"-P/dev/null",
"-b115200",
"-D",
"-V",
"-Uflash:w:/tmp/wow:i"
]
assert opts == [
into: %IO.Stream{
device: :standard_io,
line_or_bytes: :line,
raw: false
},
stderr_to_stdout: true
]
end)
Avrdude.flash("/tmp/wow", "null", fn ->
"YOLO"
end)
end
end

View File

@ -0,0 +1,8 @@
defmodule Avrdude.MuonTrapTestAdapter do
@behaviour Avrdude.MuonTrapAdapter
@impl Avrdude.MuonTrapAdapter
def cmd(exe, args, options) do
{exe, args, options}
end
end

View File

@ -1 +1,2 @@
Application.ensure_all_started(:mox)
ExUnit.start()