Tests for AVRDude / MuonTrap usage
parent
013d617538
commit
57cc8a8b37
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}
|
||||
""")
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
defmodule Avrdude.MuonTrapDefaultAdapter do
|
||||
@behaviour Avrdude.MuonTrapAdapter
|
||||
|
||||
@impl Avrdude.MuonTrapAdapter
|
||||
defdelegate cmd(exe, args, options), to: MuonTrap, as: :cmd
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +1,2 @@
|
|||
Application.ensure_all_started(:mox)
|
||||
ExUnit.start()
|
||||
|
|
Loading…
Reference in New Issue